对于 web 应用的开发, 如果你没用使用正确的工具, 那开发过程可能会变得困难和痛苦. 如果你之前开发过 Java 程序, 我相信你肯定知道 Jar 文件 (Jar 是 Java ARchive 的缩写). 一个应用, 包括所有的可执行, 可访问的文件, 都打包进了一个 JAR 文件里, 使得部署过程十分简单.
PHAR ("Php ARchive") 是 PHP 里类似于 JAR 的一种打包文件. 如果你使用的是 PHP 5.3 或更高版本, 那么 Phar 后缀文件是默认开启支持的, 你不需要任何其他的安装就可以使用它.
如果你以前没有使用过 Phar 文件, 这篇文件就是要介绍关于这种文件的一些重要特征. 希望你能发现 Phar 是一个非常有用的技术, 能给你的 PHP 开发和部署带来更快更好的体验.
PHAR 文件缺省状态是只读的, 使用 Phar 文件不需要任何的配置. 部署非常方便. 因为我们现在需要创建一个自己的 Phar 文件, 所以需要允许写入 Phar 文件, 这需要修改一下 PHP.INI
打开 PHP.INI, 找到 phar.readonly 指令行, 修改成:
phar.readonly = 0
现在, 我们就可以来把 PHP 应用打包成 Phar 文件了.
我们的第一个 PHAR 文件
首先我要按按照一个的规则创建应用的目录结构, 就行下面这样:
其中的 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
创建 PHAR 文件
在我们的 PHP 应用目录里, 需要有一个创建 Phar 文件的脚本, 我们给它起名叫 create-phar.PHP, 放置到 myapp 的跟目录下, 里面添加如下代码:
- <?PHP
- $srcRoot = "~/myapp/src";
- $buildRoot = "~/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");
打开命令行窗口, 切换到 myapp 目录下, 运行命令:
[email protected]:~/myapp$ PHP create-phar.PHP
运行了上面的命令后, 你会在 build 目录里发现一个 myapp.phar 文件, 还有一个 config.INI 的副本. 将这两个文件拷贝到 Web 服务器的服务根目录里 (e.g. htdocs).
我们可以直接访问 Phar 打包的应用, 但这需要额外配置 Web server 来将 Phar 文件发送给正确 PHP 解析器. 另外一个办法是创建一个 run 脚本, include 这个 Phar 文件.
在 Web server 的根目录创建一个叫 run.PHP 的 PHP 脚本:
- <?PHP
- require "myapp.phar";
这段代码的作用就是可以让你免去了去配置 Web server 来直接解析 Phar 文件. 如果你的应用使用的是一个共享的虚拟主机, 没有权限来配置 Web server, 那么, 这种方式是一个完美的解决方案.
创建了 run.PHP 后, 我们的 Web server 的根目录会变成这样:
打开浏览器, 将地址指向 run.PHP, 你能看到下面的输出:
Phar 的运行原理
让我们再重新看一下 create-phar.PHP 中的代码, 了解每段代码的作用. 先看看这几行:
- <?PHP
- $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 的引用就是这种方式.
- <?PHP
- require_once "phar://myapp.phar/common.php";
在 Phar 对象创建之后, index.PHP 和 common.PHP 就被加入了 Phar 文件里了. Phar 对象是一个数组, file_get_contents() 方法将各个文件的内容读到数组里. 你可以向里面添加很多的文件, 但如果你考虑添加大量的文件, 比如说整个目录下的文件, 你可以考虑使用更方便的 buildFromDirectory() 方法.
- <?PHP
- $phar->buildFromDirectory("/path/to/dir",'/.php$/');
buildFromDirectory() 的第一个参数是目录的路径; 第二个参数是可选的, 是用正则表达式过滤文件的类型. 如果目录下的所有文件都要 include, 这个参数可以忽略.
setStub() 用来创建 stub 文件, stub 文件用来告诉 Phar 在被加载时干什么.
最后, config.INI 从 src 被拷贝到 build 目录下.
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, 我们应该将哪些可能被修改的文件放到 Phar 之外, 在标准 PHP 安装里, Phar 是不允许回写的, 因为安全问题.
总结
Phar 能帮我们在打包和部署 PHP 应用上免去大量的麻烦, 我推荐大家试试它. 这篇文章只是介绍了 Phar 的一些主要概念, 包括如何创建 Phar,include 文件, 介绍 stub 文件的作用, 如何访问 Phar 里的文件.