PHP项目Phar打包与分发

wen PHP项目 2

PHP Phar 打包与分发完整指南

Phar 基础概念

Phar (PHP Archive) 是 PHP 特有的归档格式,类似于 Java 的 JAR 文件,允许将整个 PHP 应用程序打包成单个文件。

PHP项目Phar打包与分发

创建 Phar 文件

基本配置要求

// 在 php.ini 中确保以下设置
phar.readonly = Off  // 允许创建/修改 phar 文件

创建 Straw(存根)文件

<?php
// stub.php - Phar 入口文件
Phar::mapPhar('app.phar');
require 'phar://app.phar/index.php';
__HALT_COMPILER();
?>

生成 Phar 文件

<?php
// build.php - 打包脚本
$srcDir = __DIR__ . '/src';
$pharFile = 'app.phar';
// 创建 Phar 对象
$phar = new Phar($pharFile, 0, 'app.phar');
// 设置压缩格式
$phar->compressFiles(Phar::GZ);
// 或使用 BZ2 压缩
// $phar->compressFiles(Phar::BZ2);
// 开始写入文件
$phar->startBuffering();
// 设置默认的 stub,自动识别入口
$defaultStub = $phar->createDefaultStub('index.php', 'index.php');
// 添加文件
$phar->buildFromDirectory($srcDir, '/\.php$/');
// 设置存根
$phar->setStub($defaultStub);
// 添加自定义文件
$phar->addFromString('config.php', file_get_contents('config.prod.php'));
// 结束写入
$phar->stopBuffering();
echo "Phar 文件创建成功: $pharFile\n";

高级打包配置

完整打包脚本示例

<?php
// create-phar.php
class PharBuilder {
    public function build() {
        try {
            // 检查 phar.readonly
            if (ini_get('phar.readonly')) {
                throw new Exception("请设置 phar.readonly = Off");
            }
            $pharFile = 'application.phar';
            $srcDir = __DIR__ . '/app';
            // 删除已存在的 phar 文件
            if (file_exists($pharFile)) {
                unlink($pharFile);
            }
            $phar = new Phar($pharFile, FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, 'application.phar');
            $phar->startBuffering();
            // 添加所有 PHP 文件
            $phar->buildFromDirectory($srcDir, '/\.php$/');
            // 排除某些文件
            $phar->delete('config.dev.php');
            $phar->delete('test/');
            // 添加非 PHP 文件
            $phar->addFile('public/css/style.css', 'assets/style.css');
            $phar->addFile('public/js/app.js', 'assets/app.js');
            // 添加目录
            $phar->buildFromDirectory('templates/', '/\.html$/');
            // 设置文件权限
            $phar['config.php']->setMetadata(['permission' => 0644]);
            // 自定义 stub
            $stub = $this->getCustomStub();
            $phar->setStub($stub);
            $phar->stopBuffering();
            // 压缩所有文件
            $phar->compressFiles(Phar::GZ);
            echo "打包成功!文件大小: " . $this->formatSize(filesize($pharFile)) . "\n";
        } catch (Exception $e) {
            echo "打包失败: " . $e->getMessage() . "\n";
            exit(1);
        }
    }
    private function getCustomStub() {
        return <<<STUB
<?php
// 自定义入口
Phar::mapPhar('application.phar');
// 设置常量
define('PHAR_MODE', true);
define('APP_ROOT', 'phar://application.phar/');
// 设置错误显示
error_reporting(E_ALL);
ini_set('display_errors', 0);
// 设置时区
date_default_timezone_set('Asia/Shanghai');
// 加载自动加载器
require 'phar://application.phar/vendor/autoload.php';
// 运行应用
$app = new Application();
$app->run();
__HALT_COMPILER();
STUB;
    }
    private function formatSize($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        while ($bytes >= 1024 && $i < 3) {
            $bytes /= 1024;
            $i++;
        }
        return round($bytes, 2) . $units[$i];
    }
}
// 执行打包
$builder = new PharBuilder();
$builder->build();

使用 Composer 打包

<?php
// build-phar-with-composer.php
$pharFile = 'app.phar';
if (file_exists($pharFile)) {
    unlink($pharFile);
}
$phar = new Phar($pharFile, 0, 'app.phar');
$phar->startBuffering();
// 添加 Composer 自动加载和项目文件
$phar->buildFromDirectory(__DIR__ . '/vendor', '/\.php$/');
$phar->buildFromDirectory(__DIR__ . '/src', '/\.php$/');
$phar->buildFromDirectory(__DIR__ . '/config', '/\.php$/');
// 添加其他资源文件
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('public')
);
foreach ($files as $file) {
    if ($file->isFile()) {
        $relativePath = 'public/' . $file->getBasename();
        $phar->addFile($file->getPathname(), $relativePath);
    }
}
// 设置存根
$stub = "#!/usr/bin/env php\n";
$stub .= $phar->createDefaultStub('bin/console');
$phar->setStub($stub);
$phar->stopBuffering();
// 压缩
$phar->compressFiles(Phar::GZ);

Phar 文件分发

命令行工具分发

# 给予执行权限
chmod +x app.phar
# 直接运行
./app.phar
# 或通过 PHP 运行
php app.phar
# 查看帮助
php app.phar --help

Web 应用分发

<?php
// deploy.php - 部署脚本
class PharDeployer {
    public function deploy($source, $destination, $version = '1.0.0') {
        // 验证 Phar 文件
        if (!file_exists($source)) {
            throw new Exception("源文件不存在");
        }
        // 验证 Phar 签名
        $phar = new Phar($source);
        if (!$phar->isFileFormat(Phar::PHAR)) {
            throw new Exception("无效的 Phar 文件格式");
        }
        // 创建备份
        if (file_exists($destination)) {
            $backup = $destination . '.backup.' . date('YmdHis');
            copy($destination, $backup);
            echo "备份已创建: $backup\n";
        }
        // 复制文件
        if (copy($source, $destination)) {
            echo "部署成功! 版本: $version\n";
            echo "文件大小: " . filesize($destination) . " bytes\n";
            return true;
        }
        throw new Exception("部署失败");
    }
    public function verifySignature($pharFile) {
        try {
            $phar = new Phar($pharFile);
            $signature = $phar->getSignature();
            echo "签名算法: " . $signature['hash_type'] . "\n";
            echo "签名哈希: " . $signature['hash'] . "\n";
            return true;
        } catch (Exception $e) {
            echo "签名验证失败: " . $e->getMessage() . "\n";
            return false;
        }
    }
}
// 使用示例
$deployer = new PharDeployer();
$deployer->deploy('app.phar', '/usr/local/bin/myapp', '2.0.0');
$deployer->verifySignature('app.phar');

版本管理和自动更新

<?php
// update.php - 自动更新系统
class AutoUpdater {
    private $updateUrl;
    private $currentVersion;
    public function __construct($updateUrl, $currentVersion) {
        $this->updateUrl = $updateUrl;
        $this->currentVersion = $currentVersion;
    }
    public function checkForUpdates() {
        $response = file_get_contents($this->updateUrl . '/version.json');
        $versionInfo = json_decode($response, true);
        if (version_compare($versionInfo['version'], $this->currentVersion, '>')) {
            return [
                'available' => true,
                'version' => $versionInfo['version'],
                'url' => $versionInfo['download_url'],
                'checksum' => $versionInfo['checksum'],
                'release_notes' => $versionInfo['release_notes']
            ];
        }
        return ['available' => false];
    }
    public function downloadUpdate($url, $expectedChecksum) {
        $tempFile = tempnam(sys_get_temp_dir(), 'phar_update_');
        // 下载文件
        $downloaded = file_put_contents($tempFile, fopen($url, 'r'));
        if ($downloaded === false) {
            throw new Exception("下载失败");
        }
        // 验证校验和
        $actualChecksum = md5_file($tempFile);
        if ($actualChecksum !== $expectedChecksum) {
            unlink($tempFile);
            throw new Exception("校验和不匹配");
        }
        return $tempFile;
    }
    public function installUpdate($tempFile) {
        $currentFile = $_SERVER['SCRIPT_FILENAME'];
        // 创建备份
        $backupFile = $currentFile . '.backup.' . date('YmdHis');
        copy($currentFile, $backupFile);
        // 替换文件
        if (rename($tempFile, $currentFile)) {
            echo "更新成功!旧版本备份至: $backupFile\n";
            return true;
        }
        // 失败时恢复
        copy($backupFile, $currentFile);
        throw new Exception("更新失败,已恢复原文件");
    }
}
// 使用示例
$updater = new AutoUpdater('https://updates.example.com', '1.0.0');
$update = $updater->checkForUpdates();
if ($update['available']) {
    echo "发现新版本: {$update['version']}\n";
    echo "发布说明: {$update['release_notes']}\n";
    $tempFile = $updater->downloadUpdate($update['url'], $update['checksum']);
    $updater->installUpdate($tempFile);
} else {
    echo "当前已是最新版本\n";
}

性能优化和安全

性能优化

<?php
// 优化 Phar 文件使用
// 1. 使用 APC/OpCache
// 在 php.ini 中配置
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=/tmp/opcache
// 2. 使用内存缓存
$pharPath = 'phar://app.phar/config.php';
if (function_exists('apc_fetch')) {
    $config = apc_fetch('app_config');
    if ($config === false) {
        $config = require $pharPath;
        apc_store('app_config', $config, 3600);
    }
}
// 3. 延迟加载
class LazyLoader {
    private $pharFile;
    public function loadClass($className) {
        $pharPath = "phar://{$this->pharFile}/{$className}.php";
        if (file_exists($pharPath)) {
            require $pharPath;
        }
    }
}
spl_autoload_register([new LazyLoader('app.phar'), 'loadClass']);

安全加固

<?php
// 添加安全检查
class PharSecurity {
    public static function validatePhar($pharFile) {
        // 1. 验证文件完整性
        if (!self::verifyIntegrity($pharFile)) {
            return "文件完整性验证失败";
        }
        // 2. 检查签名
        $phar = new Phar($pharFile);
        $signature = $phar->getSignature();
        // 3. 检查是否篡改
        $expectedHash = 'your_expected_hash_here';
        if ($signature['hash'] !== $expectedHash) {
            return "文件签名不匹配";
        }
        // 4. 限制执行环境
        if (extension_loaded('suhosin')) {
            ini_set('suhosin.executor.include.whitelist', 'phar');
        }
        return true;
    }
    public static function secureExecution() {
        // 限制危险函数
        $disabledFunctions = [
            'exec', 'system', 'passthru', 
            'shell_exec', 'proc_open', 
            'popen', 'show_source'
        ];
        foreach ($disabledFunctions as $function) {
            if (function_exists($function)) {
                // 记录或阻止
                // 可以使用 disable_functions 在 php.ini 中配置
            }
        }
        // 设置安全目录
        define('PHAR_SAFE_MODE', true);
    }
    private static function verifyIntegrity($file) {
        // 实现文件完整性检查
        $checksumFile = $file . '.checksum';
        if (file_exists($checksumFile)) {
            $expected = file_get_contents($checksumFile);
            $actual = md5_file($file);
            return $expected === $actual;
        }
        return false;
    }
}
// 使用
PharSecurity::validatePhar('app.phar');
PharSecurity::secureExecution();

自动化构建脚本

#!/bin/bash
# build.sh - 自动化打包脚本
echo "📦 开始打包 PHP Phar..."
# 检查 PHP 版本
PHP_VERSION=$(php -r "echo PHP_VERSION;")
echo "PHP 版本: $PHP_VERSION"
# 检查 phar.readonly
PHAR_READONLY=$(php -r "echo ini_get('phar.readonly');")
if [ "$PHAR_READONLY" = "1" ]; then
    echo "❌ 需要设置 phar.readonly = Off"
    exit 1
fi
# 清理旧文件
echo "清理旧文件..."
rm -f *.phar
rm -rf build/
# 创建构建目录
mkdir -p build
# 运行测试
echo "运行测试..."
php vendor/bin/phpunit tests/ || exit 1
# 生成文档
echo "生成文档..."
php vendor/bin/phpdoc -d src -t build/docs
# 打包 Phar
echo "打包 Phar..."
php create-phar.php
# 测试 Phar
echo "测试 Phar..."
php app.phar --version || exit 1
# 生成校验和
echo "生成校验和..."
md5sum app.phar > app.phar.md5
sha256sum app.phar > app.phar.sha256
# 复制到构建目录
cp app.phar build/
cp app.phar.md5 build/
cp app.phar.sha256 build/
echo "✅ 打包完成!"
echo "文件: app.phar"
echo "大小: $(du -h app.phar | cut -f1)"

最佳实践建议

  1. 版本控制:在 Phar 文件名中包含版本号
  2. 签名验证:使用数字签名确保文件完整性
  3. 自动更新:实现自动更新机制
  4. 兼容性测试:在不同 PHP 版本上测试
  5. 错误处理:完善的错误处理机制
  6. 日志记录:记录安装和更新日志
  7. 备份机制:更新时自动创建备份

这样,您就可以完整地实现 PHP 项目的 Phar 打包和分发流程了。

抱歉,评论功能暂时关闭!