PHP打包
开发Node项目的时候,很喜欢Node的一个点就是可以通过rollup这些打包软件,把所有项目代码打包到一个js里,这样部署起来简单又方便。
本着一个问题肯定不止我一个人遇到的定理,自己也研究了一下PHP项目打包,最终实现了一个相对可行的方案。
1. 什么是Phar?
Phar 是一种 PHP 归档文件格式,类似于 Java 中的 JAR 文件,用于将多个文件打包到一个单一的文件中。
简而言之就是可以把指定目录内的一些文件打包到一个文件里,然后通过PHP执行。
2. 兼容Phar
打包成phar后,如果需要访问 Phar 归档内部的文件,必须使用 Phar 的虚拟路径。
__FILE__ 会返回 Phar 文件的路径,而 __DIR__ 会返回 Phar 文件的目录路径
3. 优缺点
Phar 文件会压缩打包的文件,这在一定程度上可以减少文件的大小,但解压过程会增加一些 CPU 负载。
所以对于php-fpm模式的项目来说,使用phar是否会带来多余的开销,需要经过实际的测试评估。
但是对于swoole这种常驻内存的项目来说,phar只会在启动的时候加载,完全不会带来多余的开销,还可以优化启动速度。
如何打包
下面的打包脚本是参照vite等打包软件的流程来编写的,打包的同时会自动删除项目内的注释,可以修改对应的配置后通过 php 脚本名称.php 来进行打包:
<?php/* 打包的配置 */$config = [ 'name' => 'app.phar', 'entry' => "easyswoole", 'src' => './swoole', 'tmp' => './tmp', 'dist' => './build', 'exclude' => [ 'build' => [ 'public', 'runtime', '*.sql', '*.phar', '*.md', '.git' ], 'comment' => [ 'Extend', 'vendor' ] ]];/** * @param $log * @return void * 输出日志 */function logEcho($log){ echo date('Y-m-d H:i:s') . ",${log}\n";}/** * @param $dir * @return bool * 清理指定目录的文件 */function deleteDirectory($dir){ if (!is_dir($dir)) { return true; } $files = array_diff(scandir($dir), ['.', '..']); foreach ($files as $file) { $path = $dir . '/' . $file; if (is_dir($path)) { deleteDirectory($path); } else { unlink($path); } } return rmdir($dir);}/* 注册关闭函数,确保在脚本中断时删除临时目录 */register_shutdown_function(function () use ($config) { if (is_dir($config['tmp'])) { deleteDirectory($config['tmp']); }});/* 创建临时目录,用于保存源文件的拷贝 */if (!is_dir($config['tmp'])) { mkdir($config['tmp'], 0777, true);}/* 删除目标目录 */if (is_dir($config['dist'])) { deleteDirectory($config['dist']); mkdir($config['dist'], 0777, true);} else { mkdir($config['dist'], 0777, true);}logEcho("开始处理文件...");/* 将需要打包的文件复制到临时目录 */$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($config['src'], \FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY);/* 开始遍历生成文件 */foreach ($files as $file) { $relativePath = str_replace($config['src'], '', $file->getPathname()); $relativePath = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath); $tempFilePath = $config['tmp'] . $relativePath; /* 检查是否排除当前文件 */ $exclude = false; $parts = explode('/', $relativePath); foreach ($config['exclude']['build'] as $pattern) { foreach ($parts as $part) { if (fnmatch($pattern, $part)) { $exclude = true; break; } } } if (!$exclude) { /* 创建临时文件的目录 */ $tempFileDir = dirname($tempFilePath); /* 创建 */ if (!is_dir($tempFileDir)) { mkdir($tempFileDir, 0777, true); } /* 复制文件到临时目录 */ copy($file->getPathname(), $tempFilePath); /* 移除注释 */ $comment = true; $ext = pathinfo($tempFilePath)['extension']; /* 是否匹配当前路径 */ foreach ($config['exclude']['comment'] as $pattern) { foreach ($parts as $part) { if (fnmatch($pattern, $part)) { $comment = false; break; } } } /* 输出文件 */ logEcho(str_replace(DIRECTORY_SEPARATOR, '/', $file->getPathname())); }}/* 输出日志 */logEcho("开始打包...");/* 创建 Phar 文件 */$pharPath = $config['dist'] . '/' . $config['name'];$phar = new Phar($pharPath, 0, $config['name']);$phar->buildFromDirectory($config['tmp']);/* 设置 Phar 的入口文件 */$phar->setStub($phar->createDefaultStub($config['entry']));/* 清理临时目录 */deleteDirectory($config['tmp']);/* 输出日志 */logEcho("打包成功!");