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 中運行的應用程式時應考慮以下幾點:
任何需要創建的特定於應用程式實例的文件,例如config 文件,都不能作為歸檔的一部分,因此需要將它們寫入獨立但是可訪問的位置。如果應用程式建立構成擴充功能的快取文件,那麼這些文件也要採用相同的做法。
您應始終使用基於 Phar 的歸檔格式,並且不對歸檔中的檔案進行壓縮,從而獲得最大的靈活性。基於 ZIP 和 TAR 的歸檔要求在 PHP 中安裝 Phar 擴展,而基於 Phar 的歸檔甚至可用於未安裝 Phar 擴展的情況。
應用程式中的任何檔案引用都需要修改為同時使用 phar:// 流包裝器和歸檔名,如前面小節所示。
PHPMyAdmin 是一種流行的 PHP 應用程序,它一直使用 Phar 打包,演示出使用 Phar 歸檔的簡便性。它一直以來被設計為從 Phar 歸檔文件運行,但是仍然能夠在 Phar 歸檔之外存儲配置文件。