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 檔案需要哪些步驟。應用程式的設計目標是從 網頁瀏覽器或命令提示字元直接載入。第一步是建立 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() 方法的第二個選擇性參數允許包含一個不同的檔案。這對於設計用於命令列或 網頁瀏覽器內容相關的應用程式非常方便。
對於基於 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() 方法,該方法將遍曆指定的目錄並添加其中的檔案。它還支援對添加的檔案進行過濾,方法是使用檔案的Regex模式傳遞第二個參數,以匹配檔案並添加到歸檔中。清單 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 歸檔之外儲存設定檔。