首页 > PHP开发 > php中级 > PHP扩展之压缩与归档扩展2——Phar
2014
11-07

PHP扩展之压缩与归档扩展2——Phar

Phar 归档的概念来自 Java™ 技术的 JAR 归档,它允许使用单个文件打包应用程序,这个文件中包含运行应用程序所需的所有东西。该文件不同于单个可执行文件,后者通常由编程语言生成,比如 C,因为该文件实际上是一个归档文件而非编译过的应用程序。因此 JAR 文件实际上包含组成应用程序的文件,但是考虑到安全性,不对这些文件进行仔细区分。Phar 扩展正是基于类似的理念,但是在设计时主要针对 PHP 的 Web 环境。同样,与 JAR 归档不同的是,Phar 归档可由 PHP 本身处理,因此不需要使用额外的工具来创建或使用。

Phar 扩展对 PHP 来说并不是一个新鲜的概念。它最初使用 PHP 编写并被命名为 PHP_Archive,然后在 2005 年被添加到 PEAR 库。然而在实际中,解决这一问题的纯 PHP 解决方案非常缓慢,因此 2007 年重新编写为纯 C 语言扩展,同时添加了使用 SPL 的ArrayAccess 对象遍历 Phar 归档的支持。自那时起,人们做了大量工作来改善 Phar 归档的性能。

创建 Phar

创建 Phar 文件需要执行若干步骤。所有步骤需要用到某种形式的 PHP 命令完成创建,因为不存在用于创建归档的独立工具。此外,要创建和修改 Phar 文件,php.ini 设置 phar.readonly 必须被设置为 0。在 PHP 的 Phar 归档内打开和引用文件时不需要使用到该设置。

让我们看一看创建可用于驱动应用程序的 Phar 文件需要哪些步骤。应用程序的设计目标是从 Web 浏览器或命令提示符直接加载。第一步是创建 Phar 文件,因此我们将创建清单 1 所示的 Phar 对象。对象引用将允许您控制 Phar 归档的所有方面。

示例 1. 创建 Phar 对象

$p = new Phar('/path/to/my.phar', CURRENT_AS_FILEINFO | KEY_AS_FILENAME, 'my.phar');
$p->startBuffering();

构造函数的第一个参数表示保存 Phar 文件的位置。第二个参数将所有参数都传递给 RecursiveDirectoryIterator 父类。第三个参数是在流上下文中引用 Phar 归档的别名。因此对于清单 1,可以在这个 Phar 归档中使用 phar://my.phar 引用文件。您还可以发出Phar::startBuffering() 方法调用来缓冲对归档做出的修改,直到发出 Phar::stopBuffering() 命令为止。尽管不一定要执行上述操作,但是这样做确实改善了创建或修改归档的性能,因为它避免了每次在脚本中修改归档时对做出的修改进行保存。

默认情况下,创建的 Phar 将使用原生的基于 Phar 的归档格式。还可以按照清单 2 所示将格式转换为 ZIP 格式,从而对 Phar 文件使用 ZIP 或 TAR 格式。
示例 2. 将存储格式转换为 ZIP 格式

$p = $p->convertToExecutable(Phar::ZIP);

转换归档格式有利也有弊。主要优点就是能够使用任何处理 ZIP 或 TAR 文件的工具查看归档的内容。然而,如果 Phar 归档没有使用原生的基于 Phar 的归档格式,那么它不需要使用 Phar 扩展加载归档,而使用 ZIP 或 TAR 格式的 Phar 归档则需要如此。

接下来,将需要定义文件存根(stub),这是在加载 Phar 文件时首先调用的代码。

Phar 文件存根

文件存根仅仅是在加载 Phar 文件时最初运行的代码的一小部分,并且始终以一个 __HALT_COMPILER() 标记作为结束。清单 3 展示了一个典型的文件存根。
示例 3. Phar 文件存根

<?php
    Phar::mapPhar();
    include 'phar://myphar.phar/index.php';
    __HALT_COMPILER();

上面所示的 Phar::mapPhar() 方法调用通过读取清单文件(manifest)对 Phar 归档执行初始化。您需要在归档内引用文件之前使用 phar:// 流包装器执行初始化。初始加载的文件将是应用程序首次加载时的文件;在本例中为 index.php。

如何将这个文件存根 Phar 添加到 Phar 归档取决于所使用的归档的格式。对于基于 Phar 的归档,使用 Phar::setStub() 方法,它将接受 PHP 代码的惟一参数,并以字符串形式放入存根中。清单 4 演示了这一方法。
示例 4. 使用 Phar::setStub() 创建文件存根

$p->setStub('<?php Phar::mapPhar(); 
include 'phar://myphar.phar/index.php'; __HALT_COMPILER(); ?>');

如果您计划使用存根而不是重定向到 index.php 页面来完成操作,可以使用 helper 方法 Phar::createDefaultStub() 构建文件存根。因此,只需要传递您希望包含在文件存根的文件的名称。在清单 5 中,将重写 Phar::setStub() 方法调用来使用 helper 方法。

示例 5. 使用 Phar::createDefaultStub() 创建文件存根

$p->setStub($p-> createDefaultStub('index.php'));

如果从 Web 服务器加载 Phar,Phar::createDefaultStub() 方法的第二个可选参数允许包含一个不同的文件。这对于设计用于命令行或 Web 浏览器上下文的应用程序非常方便。

对于基于 ZIP 和 TAR 的实现,将以上存根的内容存储到 .phar/stub.php 文件内,而不是使用 setStub() 命令。

将文件添加到归档

Phar 对象使用 ArrayAccess SPL 对象,允许以数组的形式访问归档内容,因此提供了许多方法来向归档添加文件。最简单的方法是直接使用 ArrayAccess 接口。
示例 6. 向归档添加文件

$p['file.txt'] = 'This is a text file';
$p['index.php'] = file_get_contents('index.php');

示例 6 表明文件名被指定为数组键,将内容指定为值。可以使用 file_get_contents() 函数获得现有文件的内容,然后将内容设为值。这样可以更加灵活地向归档添加文件,可以通过引用现有文件或动态构建文件实现。后一种方法可以作为应用程序构建脚本的一部分。

如果存储在 Phar 归档中的文件非常大,可以分别通过 PharFileInfo::setCompressedGZ() 或PharFileInfo::setCompressedBZIP2() 方法使用 gzip 或 bzip2 压缩有选择地压缩归档中的文件。在清单 7 中,您将使用 bzip2 压缩文件。
示例 7. 使用 bzip2 压缩 Phar 归档中的文件

$p['big.txt'] = 'This is a big text file';
$p['big.txt']->setCompressedBZIP2();

要压缩文件或使用包含压缩文件的归档,必须在 PHP 安装中支持 bzip2 或 zlib(用于 gz 压缩文件)扩展。

假设您需要将许多文件加入到归档中。使用 ArrayAccess 接口逐一添加文件是一项非常单调的工作,因此可以使用一些便捷的方法。一种方法就是使用 Phar::buildFromDirectory() 方法,该方法将遍历指定的目录并添加其中的文件。它还支持对添加的文件进行过滤,方法是使用文件的正则表达式模式传递第二个参数,以匹配文件并添加到归档中。清单 8 展示了这一过程。
示例 8. 使用 Phar::buildFromDirectory() 向归档添加文件

$p->buildFromDirectory('/path/to/files','./\.php$/');

示例 8 将指定目录中的 PHP 文件添加到 Phar 归档。如果需要对添加的文件执行任何修改,比如将文件压缩,那么可以使用ArrayAccess 接口返回。

可以使用一个迭代器(iterator)通过 Phar::buildFromIterator() 方法添加文件。支持两种风格的迭代器:一种是将 Phar 中的文件名映射到磁盘文件的名称,另一种是返回 SplFileInfo 对象。RecursiveDirectoryIterator 是一种兼容的迭代器,下面展示如何使用它向归档添加目录文件。
示例 9. 使用 Phar::buildFromIterator() 向归档添加目录文件

$p->buildFromIterator(new RecursiveIteratorIterator
(new RecursiveDirectoryIterator('/path/to/files')),'/path/to/files');

Phar::buildFromIterator() 方法接受迭代器对象本身作为惟一的参数。在上例中,您已经使用RecursiveIteratorIterator 对象包装了 RecursiveDirectoryIterator 对象,RecursiveIteratorIterator 对象提供了Phar::buildFromIterator() 方法所需的兼容型迭代器。

我们现在已经创建了一个 Phar 归档,它可以用于任何 PHP 应用程序。让我们看一看如何方便地使用这个归档。

使用 Phar 归档

Phar 归档的一个优点就是可以非常方便地集成到任何应用程序中。如果使用的是原生的基于 Phar 的归档格式,这一点尤其明显。在这种情况下,您甚至不需要安装 Phar 扩展,因为 PHP 天生就可以加载文件并提取文件内容。基于 ZIP 和 TAR 的归档需要加载 Phar 扩展。

Phar 归档在设计时被包括到应用程序中,跟普通的 PHP 文件一样,这使得已经熟悉如何包含其他第三方代码的应用程序开发人员可以非常方便地使用 Phar 归档。让我们看一看在应用程序中集成 Phar 有多么容易。

在应用程序中集成 Phar 归档代码

在 Phar 归档中集成代码的最简单方法就是包含 Phar 归档,然后在 Phar 文件中包含需要使用的文件。phar:// 流包装器可以用来访问已加载 Phar 归档中的文件,如下所示。
示例 10. 在 Phar 归档中加载代码

include 'myphar.phar';  
include 'phar://myphar.phar/file.php';

第一个 include 将加载 myphar.phar 归档,包含文件存根中指定的代码。第二个 include 使用流包装器打开 Phar 归档并且仅在归档中包括指定的文件。注意在归档中包含文件之前,您不需要包含 Phar 归档本身,如清单 10 所示。

从 Phar 归档运行 PHP 应用程序

Phar 归档的一个出色特性就是可以使用一个 Phar 归档打包整个应用程序并进行发布。这种方法的优点就是简化应用程序部署并且不会降低性能,它主要得益于 PHP V5.3 中新增的若干 Phar 增强。然而,设计在 Phar 中运行的应用程序时应当考虑以下几点:

  1. 任何需要创建的特定于应用程序实例的文件,比如 config 文件,都不能作为归档的一部分,因此需要将它们写入到独立但是可访问的位置。如果应用程序创建构成扩展的缓存文件,那么这些文件也要采用相同的做法。
  2. 您应当始终使用基于 Phar 的归档格式,并且不对归档中的文件进行压缩,从而获得最大的灵活性。基于 ZIP 和 TAR 的归档要求在 PHP 中安装 Phar 扩展,而基于 Phar 的归档甚至可用于未安装 Phar 扩展的情况。
  3. 应用程序中的任何文件引用都需要修改为同时使用 phar:// 流包装器和归档名,如前面小节所示。

PHPMyAdmin 是一种流行的 PHP 应用程序,它一直使用 Phar 打包,演示出使用 Phar 归档的简便性。它一直以来被设计为从 Phar 归档文件运行,但是仍然能够在 Phar 归档之外存储配置文件。

编程技巧