Phar 构建工具

2025-08-07 php composer, phar

背景

在 PHP 开发中,我们经常需要使用各种第三方包来加速开发进程。然而,在某些情况下,我们可能会遇到以下问题:

  1. 项目依赖的某个包需要更新,但直接更新可能会破坏现有代码的兼容性
  2. 我们需要将自己的项目打包成一个独立的文件,方便分发给其他开发者使用
  3. 我们希望在不影响现有项目的情况下,测试某个包的新版本

Phar (PHP Archive) 文件是一种将多个 PHP 文件和资源打包成单个文件的格式,它可以帮助我们解决上述问题。通过将项目或库打包成 Phar 文件,我们可以:

  • 保持依赖版本的稳定性
  • 方便地分发和部署应用程序
  • 在不影响现有项目的情况下测试新的包版本

本工具提供了一个简单的方式来创建自定义的 Phar 文件,无需强制更新现有依赖。

代码实现

以下是 builder.php 文件的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

class Builder
{
/**
* @var Phar
*/
protected $phar;
/**
* @var string[]
*/
protected $exclude = [
'composer.lock',
'.idea',
'.git',
'.vscode'
];
/**
* @var string
*/
protected $index = 'index.php';

/**
* 构造函数
* @param string $fname 要创建的 Phar 文件路径
*/
public function __construct($fname)
{
// 如果文件已存在,则删除它
if (is_file($fname)) {
unlink($fname);
}
// 创建新的 Phar 对象
$this->phar = new \Phar($fname, FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME);
}

/**
* 创建自动加载文件
* 如果入口文件不存在,则创建一个包含自动加载代码的入口文件
*/
public function autoloadfile()
{
if (!is_file($this->index)) {
$text = '<?php' . PHP_EOL;
$text .= 'if (is_file(__DIR__ . \'/vendor/autoload.php\')) {' . PHP_EOL;
$text .= 'require __DIR__ . \'/vendor/autoload.php\';' . PHP_EOL;
$text .= '}';
file_put_contents($this->index, $text);
}
}

public function buildExcludeRegex()
{
$this->exclude[] = __FILE__;
$escaped = array_map(function ($v) {
return preg_quote($v, '/');
}, $this->exclude);
// 构造正则:排除匹配到任意一个文件名
return '/^(?!.*(' . implode('|', $escaped) . ')).*$/i';
}

/**
* 构建 Phar 文件
* @param string $path 要打包的目录路径
*/
public function build($path)
{
// 确保自动加载文件存在
$this->autoloadfile();
// 开始缓冲 Phar 写入操作
$this->phar->startBuffering();
// 从指定目录构建 Phar 文件,排除匹配正则表达式的文件
$pattern = $this->buildExcludeRegex();
$this->phar->buildFromDirectory($path, $pattern);
// 设置 Phar 文件的存根(入口点)
$this->phar->setStub($this->phar->createDefaultStub($this->index));
// 停止缓冲并写入 Phar 文件
$this->phar->stopBuffering();
}
}

if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "错误:此脚本必须通过命令行执行!\n");
exit(1);
}
$pharFile = basename(__DIR__);
if (isset($argv[1])) {
$pharFile = $argv[1];
}
$filename = $pharFile . '.phar';
(new Builder(__DIR__ . DIRECTORY_SEPARATOR . $filename))->build(__DIR__);

本地运行生成 Phar 文件

步骤 1: 准备独立SDK(guzzlehttp/guzzle)

1
2
mkdir guzzle && cd guzzle
composer require guzzlehttp/guzzle:^7.0

步骤 2:

将上述代码保存为 build.php 文件,并放置在您要打包的 PHP 项目根目录下。

步骤 2: 运行构建脚本

打开终端,导航到项目根目录,运行以下命令:

1
2
3
4
5
# 基本用法 - 生成与目录同名的 Phar 文件
php -d phar.readonly=0 build.php

# 自定义文件名 - 生成指定名称的 Phar 文件
php -d phar.readonly=0 build.php guzzle

步骤 3: 验证生成的文件

运行成功后,您会在项目根目录下看到生成的 .phar 文件(如 guzzle.phar)。

使用 Phar 文件

生成 Phar 文件后,您可以通过以下方式使用它:

包含到其他项目中

您可以在其他 PHP 项目中包含这个 Phar 文件:

1
2
3
4
5
6
7
8
9
<?php
require 'guzzle.phar';
use GuzzleHttp\Client;
$client = new Client();
// 发送 GET 请求
$response = $client->request('GET', 'https://www.zhiqiang.wang');
// 获取响应状态码
$statusCode = $response->getStatusCode();
echo "Status code: $statusCode\n";

作为命令行工具使用

如果您的 Phar 文件设计为命令行工具,可以通过以下方式运行:

注意事项

  1. 运行脚本时需要禁用 phar.readonly 选项(使用 -d phar.readonly=0
  2. 确保您有写入当前目录的权限
  3. 如果您的项目有特殊的自动加载需求,可能需要手动修改生成的 index.php 文件
  4. Phar 文件打包后,原始代码的路径结构会被保留,因此请确保您的代码中使用的是相对路径

扩展建议

  • 添加更多命令行参数选项(如指定排除文件列表、压缩级别等)
  • 支持自定义入口脚本内容
  • 添加版本号和元数据信息到 Phar 文件中
  • 实现增量构建功能,只打包修改过的文件