PHP Phar使用


Phar使用


正文

什么是Phar

如果你之前开发过Java程序,我相信你肯定知道Jar文件(Jar是Java ARchive的缩写)。一个应用,包括所有的可执行、可访问的文件, 都打包进了一个JAR文件里,使得部署过程十分简单。

PHAR (“PHp ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的, 你不需要任何其他的安装就可以使用它。

phar扩展提供了一种方法,可以将整个PHP应用程序放入一个名为“phar”(PHP归档)的文件中,以便轻松分发和安装。除了提供此服务外, phar扩展还提供了一种文件格式抽象方法,用于通过PharData类创建和操作tar和zip文件,就像PDO提供了一个访问不同数据库的统一接口一样。 与PDO不同,Phar不能在不同的数据库之间转换,它还可以用一行代码在tar、zip和Phar文件格式之间转换。

Phar归档的特点是将多个文件归组为一个文件的方便方法。因此,phar归档提供了一种方法,可以在单个文件中分布完整的PHP应用程序,并从该文件运行它, 而无需将其解压缩到磁盘。此外,PHP可以像命令行和web服务器上的任何其他文件一样轻松地执行phar归档。Phar有点像PHP应用程序的U盘驱动器。

Phar通过流包装器实现此功能。通常,要在PHP脚本中使用外部文件,您将使用 include() 方法。

配置

PHAR文件缺省状态是只读的,使用Phar文件不需要任何的配置,部署非常方便。因为需要允许写入Phar文件,这需要修改一下 php.ini。

打开 php.ini,找到 phar.readonly 指令行,修改成:

phar.readonly = 0

现在,我们就可以来把PHP应用打包成Phar文件了。

通过php.ini的配置项,可以设定一些Phar的函数行为方式。

配置项 默认值 解释
phar.readonly 1 只允许读,在php.ini中取消设置
phar.require_hash 1 强制所有打开的Phar包需要包含某种签名,否则拒绝处理,在php.ini中取消设置
phar.extract_list   从phar 2.0.0开始,此INI设置已被删除,加载路径映射
phar.cache_list   允许在Web服务器启动时预先解析映射phar存档,从而提供性能改进,使运行文件从phar存档中移出,非常接近从传统的基于磁盘的安装运行这些文件的速度。

打包PHAR文件

我们先建立以下层级的文件

myapp
├── build 
├── src
|   └── common.php
|   └── config.ini
|   └── index.php
└── create-phar.php   

其中的 build 目录里将放置 PHAR 文件,这样能避免它跟源码程序混合到一起。src 放的就是我们的PHP源码。

index.php 会成为我们的应用的入口程序,common.php 可以放置应用需要的一些共有代码,config.ini 是我们的配置文件。

index.php 大概是这样样子:

<?php
require_once "phar://myapp.phar/common.php";
$config = parse_ini_file("config.ini");
AppManager::run($config);

common.php 大概是这样的:

<?php
class AppManager
{
    public static function run($config) {
         echo "Application is now running with the following configuration... ";
         var_dump($config);
    }
}

config.ini 的内容是这样的:

[database]
host=localhost
db=dbname
user=myuser
pass=dbpass

create-phar.php 是创建Phar文件的脚本:

<?php
$srcRoot = "/var/www/myapp/src";
$buildRoot = "/var/www/myapp/build";
 
$phar = new Phar($buildRoot ."/myapp.phar", FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, "myapp.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 文件

打开命令行窗口,切换到 myapp 目录下,运行命令:

~/myapp$ php create-phar.php

运行了上面的命令后,你会在 build 目录里发现一个 myapp.phar 文件,还有一个config.ini 文件。

将这两个文件拷贝到WEB服务器的项目目录下。

项目目录下创建一个叫 run.php 的PHP脚本,include 这个Phar文件:

<?php
require "myapp.phar";

打开浏览器,将地址指向 run.php,或者命令行运行php run.php,你能看到下面的输出:

Application is now running with the following configuration... array(4) {
  ["host"]=>
  string(9) "localhost"
  ["db"]=>
  string(6) "dbname"
  ["user"]=>
  string(6) "myuser"
  ["pass"]=>
  string(6) "dbpass"
}

运行原理

$phar = new Phar($buildRoot ."/myapp.phar", FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, "myapp.phar");

一个新 Phar 对象的创建通常需要三个参数。第一个参数是Phar文件的路径。你不仅可以通过它创建Phar文件,还可以对现存的Phar文件进行操作。

第二个参数是设定 Phar 对象如何处理文件。Phar 对象继承了 PHP RecursiveDirectoryIterator 对象,这个参数是直接传递到父类里。 这里提供的值是RecursiveDirectoryIterator 的缺省值,能满足目前的要求。

第三个参数是Phar文件的别名,在内部引用这个Phar文件时都要使用这个别名。也就是说,Phar内部文件的相互include都需要显式的使用这个别名。 例如,之前的index.php 对 common.php 的引用就是这种方式。

require_once "phar://myapp.phar/common.php";

Phar 对象是一个数组,file_get_contents() 方法将各个文件的内容读到数组里。你可以向里面添加很多的文件,但如果你考虑添加大量的文件, 比如说整个目录下的文件,你可以考虑使用更方便的 buildFromDirectory() 方法。

<?php
$phar->buildFromDirectory("/path/to/dir", '/.php$/');

buildFromDirectory() 的第一个参数是目录的路径;第二个参数是可选的,是用正则表达式过滤文件的类型。 如果目录下的所有文件都要include,这个参数可以忽略。

在Phar对象创建之后, index.php 和 common.php 就被加入了Phar文件里了。

setStub() 用来创建stub文件,stub文件用来告诉Phar在被加载时干什么。

Stub文件

运行Phar文件时,stub文件被当做一个meta文件来初始化Phar, 并告诉Phar文件在被调用时该做什么。 在我们的例子中,使用的是 createDefaultStub() 方法,生成的缺省stub文件包含如下的代码:

<?php
Phar::mapPhar();
include "phar://myapp.phar/index.php";
__HALT_COMPILER();

createDefaultStub() 方法缺省创建的stub文件的内容很简单。 Phar::mapPhar() 用来分析Phar文件的元数据,并初始化它。 stub文件的结尾处需要调用 __HALT_COMPILER() 方法,这个方法后不能留空格。 __HALT_COMPILER() 会立即终止PHP的运行,防止include的文件在此方法后仍然执行。这是Phar必须的,没有它Phar将不能正常运行。

除此之外,我们还可以创建自己的stub文件来执行自定义的初始化过程,像这样加载自定义文件:

<?php
$phar->setStub(file_get_contents("stub.php"));

限制

如果你开发的类库或可引用程序需要被多个项目使用,把它打包成Phar文件是一个省事的解决方案。 Phar文件是经过高度优化过的,它和普通文件的执行效率相比完全不弱,所以,你不需要担心效率问题。

但需要注意的是,Phar文件的使用有一些限制。下面是一些提示让你更好的理解它:

  • 我们可以整个应用都打包到一个Phar文件里,但Phar只提供了一个单一入口。
  • 在生产环境里,我们应该避免将东西回写进Phar,我们应该将哪些可能被修改的文件放到Phar之外,在标准PHP安装里,Phar是不允许回写的,因为安全问题。






参考资料

PHP 手册 函数参考 压缩与归档扩展 Phar https://www.php.net/phar

PHP开发常识:什么是Phar? https://www.webhek.com/post/packaging-your-php-apps-with-phar.html

PHP:Phar的打包和使用 http://www.php20.cn/article/179

使用phar上线你的代码包 https://segmentfault.com/a/1190000002166235


返回