簡單了解Phar代碼打包工具的使用
Phar 是在 PHP5 之后提供的一種類似於將代碼打包的工具。本質上是想依照 Java 的 Jar 文件那種形式的代碼包,不過本身由於 PHP 是不編譯的,所以這個 Phar 實際上就是將代碼原樣的進行打包,不會進行編譯。但是我們可以對打包的 Phar 包進行壓縮操作。
另外,實際上使用過 Phar 包的人非常少,特別是在 Composer 已經成為事實代碼庫標准的今天,Phar 就更加難覓蹤影了。不過,Composer 的安裝包本身也是一個 .phar 的打包文件。最主要的原因,一個是 Phar 這種形式的代碼包安裝並不像 Composer 一樣的簡單方便,另一方面,早期的程序員,特別是 LAMP/LAMP 的程序員,都喜歡去將開源的代碼復制過來,而不喜歡直接使用一個工具包。畢竟,源代碼在手上讓我們更加踏實一些。其實,就算是 Composer 這樣直接下載的就是源碼,我們也從來沒什么人真正的去翻過。而 Composer 相比 Phar 的最大優勢,一個是代碼的自動加載,另一個就是標准的 PSR 命令空間和目錄規范。這兩個在 Phar 中是沒有的,所以我們要使用 Phar 包都必須要 require 一下。
雖說已經過時了,但我們還是簡單的來學習了解一下。說不定在什么時候我們就能用上,特別是封裝一些內部的公用庫函數時,Phar 打包代碼的這種方式還是非常有用的。
代碼打包
我們先按標准格式建立一個目錄樹。
在這個目錄樹中,src 目錄存放源碼,build 目錄用來存放生成后的 .phar 代碼包。
// index.php
<?php
require_once "phar://myphar.phar/common.php";
index.php 文件中,我們就是簡單的引用 common.php 。注意這里使用的是 phar 偽協議來加載的 common.php 文件。關於偽協議的內容我們之前有過一篇文章進行過講解。
<?php
// common.php
class Manager{
public static function run($config){
echo "AAA", PHP_EOL;
var_dump($config);
}
public static function ChineseMobile($mobile){
if(preg_match("/^1[34578]\d{9}$/", $mobile)){
return true;
}
return false;
}
}
common.php 文件中只是提供了一個類和兩個簡單的方法用來測試。run() 方法就是簡單的輸出打印的內容和傳遞過來的參數。ChineseMobile() 方法則是我們提供的一個判斷我們國內手機號的函數。
[database]
host=localhost
db=dbname
user=myuser
pass=dbpass
config.ini 是一個配置文件,其實我們可以在 Phar 的代碼中直接的進行配置文件的讀取,也可以讓配置文件隨代碼一起 build 到指定的目錄。
源碼文件准備好了,接下來就是要准備打包的編譯文件了。
// create-phar.php
$srcRoot = "./myphar/src";
$buildRoot = "./myphar/build";
$phar = new Phar($buildRoot . "/myphar.phar",
FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, "myphar.phar");
$phar["index.php"] = file_get_contents($srcRoot . "/index.php");
$phar["common.php"] = file_get_contents($srcRoot . "/common.php");
$phar->setStub($phar->createDefaultStub("index.php"));
copy($srcRoot . "/config.ini", $buildRoot . "/config.ini");
代碼並不復雜,主要是一個 Phar 類,這個類要指定生成文件的目錄,文件名,然后使用 createDefaultStub() 方法來調用我們包的入口文件 index.php ,這個方法是用於創建指定的 .phar 文件的存根。其實就是指定一個入口文件,就像 Java 中的 main() 方法入口一樣。
然后我們拷貝了 config.ini 文件到發布目錄 build 中。
接着使用命令行運行這個 create-phar.php 文件,就能夠生成這套代碼包了。
# php ./create-phar.php
使用文本編輯器打開 myphar.phar 文件,我們會發現里面竟然還是我們熟悉的 PHP 代碼,拉到最底下,更會發現 index.php 和 common.php 的內容都被編譯在這個文件中了。上面的那些自動生成的代碼就是一些引導或者前置准備語句,是 Phar 擴展為我們准備好的內容,所有用戶自己寫的源碼都會在這個文件的底部。也就是說,大家可以下載 Composer 的安裝包,也就是那個 .phar 文件看看里面都寫了什么東西。
接下來就是使用了,這個就非常簡單了。
$config = parse_ini_file("./myphar/build/config.ini");
require './myphar/build/myphar.phar';
Manager::run($config);
// AAA
// array(4) {
// ["host"]=>
// string(9) "localhost"
// ["db"]=>
// string(6) "dbname"
// ["user"]=>
// string(6) "myuser"
// ["pass"]=>
// string(6) "dbpass"
// }
var_dump(Manager::ChineseMobile('13811111111'));
var_dump(Manager::ChineseMobile('138111111112'));
// bool(true)
// bool(false)
壓縮能力
前面說過,做為代碼庫來說,Phar 已經早就敗給了 Composer ,但是它除了能夠做為一些安裝包來使用之外,本身 Phar 也是一個壓縮工具。可以用來存檔一些文件、文本、目錄之類的內容。下面我就來簡單看看對於文本的存檔,Phar 是如何使用的。
unlink('./my.phar');
unlink('./my.phar.bz2');
unlink('./my.phar.gz');
$p = new Phar('./my.phar', 0 ,'my.phar');
$p['myfile1.txt'] = 'hi1';
$p['myfile2.txt'] = 'hi2';
$p1 = $p->compress(Phar::GZ);
$p2 = $p->compress(Phar::BZ2);
unset($p);
$decompressPhar = new Phar('./my.phar', 0 ,'my.phar');
foreach($decompressPhar as $file){
// $file 是返回的 PharFileInfo 對象
var_dump($file->getFileName());
var_dump($file->isCompressed());
var_dump($file->isCompressed(Phar::BZ2));
var_dump($file->isCompressed(Phar::GZ));
var_dump($file->getContent());
}
echo '==================', PHP_EOL;
// string(11) "myfile1.txt"
// bool(false)
// bool(false)
// bool(false)
// string(3) "hi1"
// string(11) "myfile2.txt"
// bool(false)
// bool(false)
// bool(false)
// string(3) "hi2"
首先,依然是實例化一個 Phar 類,然后我們給它像數組一樣增加屬性,這樣,屬性內容就被打包進了 .phar 文件中。通過直接查看 my.phar 文件,我們可以看出,myfile1.txt 這兩個屬性直接被寫成了文件進行保存了,也就是說,它幫我們將文本轉化成文件並打包在了 my.phar 這個壓縮包文件中了。
compress() 方法則是將當前的這個 Phar 對象壓縮存儲為某個格式的文件。這里我們直接壓縮了 Bzip2 和 GZ 文件。調用這個方法后直接就會生成對應的壓縮文件。
Phar 對象在遍歷時產生的對象是 PharFileInfo 對象,它擁有很多類似於 File 的文件操作函數。大家可能在官方文檔中找到相關的說明。
假設我們遍歷 my.phar.gz ,內容依然可以正常輸出,但循環中的 isCompressed() 判斷都依然會是 false ,難道文件沒有被壓縮嗎?其實,我們需要通過另一個函數來讓所有文件都進行統一格式的壓縮。
$p = new Phar('./my.phar', 0 ,'my.phar');
$p->compressFiles(Phar::GZ);
unset($p);
$decompressPhar = new Phar('./my.phar.gz', 0 ,'my.phar');
foreach($decompressPhar as $file){
// $file 是返回的 PharFileInfo 對象
var_dump($file->getFileName());
var_dump($file->isCompressed());
var_dump($file->isCompressed(Phar::BZ2));
var_dump($file->isCompressed(Phar::GZ));
var_dump($file->getContent());
}
echo '==================', PHP_EOL;
// string(11) "myfile1.txt"
// bool(true)
// bool(false)
// bool(true)
// string(3) "hi1"
// string(11) "myfile2.txt"
// bool(true)
// bool(false)
// bool(true)
// string(3) "hi2"
使用 compressFiles() 對整個 .phar 中的所有文件進行了統一的格式壓縮之后,再打印時 isCompressed() 就會返回對應格式的 true 了。
數據格式 Phar
最后,如果只是為了打包壓縮功能的話,我們沒必要使用 Phar 類。Phar 類最主要的還是用來打包能夠運行的 PHP 源碼,也就是它的 createDefaultStub() 方法非常重要。而如果只是打包普通文件的話,我們並不需要這個方法,這時,我們就可以使用另外一個 PharData 類來進行數據的打包壓縮。使用方法和 Phar 類是一模一樣的。同時,PharData 類可以直接打包成 tar 之類的文件。
$p = new PharData('./myData.tar');
$p['myfile1.txt'] = 'hi1';
$p['myfile2.txt'] = 'hi2';
foreach($p as $file){
var_dump($file->getFileName());
var_dump($file->isCompressed());
var_dump($file->isCompressed(Phar::BZ2));
var_dump($file->isCompressed(Phar::GZ));
var_dump($file->getContent());
}
echo '==================', PHP_EOL;
// string(11) "myfile1.txt"
// bool(false)
// bool(false)
// bool(false)
// string(3) "hi1"
// string(11) "myfile2.txt"
// bool(false)
// bool(false)
// bool(false)
// string(3) "hi2"
總結
說實話,Phar 真的是一個冷門項目,但是在某些情況中又非常有用,比如它雖然在代碼包領域被 Composer 打敗了,但是它又可以成為 Composer 的安裝包,也就是說,沒有 Phar 你就安裝不了 Composer 。而做為壓縮工具,雖然有強大的實力但使用的卻也非常的少。因此,我們還是以了解為目的,如果感覺某些場景非常合適的話,也完全可以深入的研究拿來放到我們的實際項目中使用。畢竟它是 PHP 的一部分,不需要任何的編譯安裝及其它支持,非常原生。
測試代碼:
參考文檔:
https://www.php.net/manual/zh/book.phar.php
https://www.webhek.com/post/packaging-your-php-apps-with-phar.html
http://www.mamicode.com/info-detail-888559.html
===============
關注公眾號:【硬核項目經理】獲取最新文章
添加微信/QQ好友:【xiaoyuezigonggong/149844827】免費得PHP、項目管理學習資料
知乎、公眾號、抖音、頭條搜索【硬核項目經理】
B站ID:482780532