PHP源代码如何打包成一个单独的文件?
- PHP笔记
- 11分钟前
- 9热度
- 0评论
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' => './server', //打包的目录
'tmp' => './tmp', //临时目录
'dist' => './build', //打包输出的目录
'exclude' => [
/* 排除不打包的文件和目录 */
'build' => [
'public',
'runtime',
'*.sql',
'*.phar',
'*.md',
'.git',
'*.stackdump'
],
/* 排除不移除文件注释的目录 */
'comment' => [
'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;
}
}
}
/* 移除注释 */
if ($ext === 'php' && $comment) {
/* 对文件进行二次处理(例如修改文件内容) */
$content = file_get_contents($tempFilePath);
/* 移除所有多行注释 */
$preg_1 = '#/\*[\s\S]*?\*/#u'; // 正则表达式:匹配多行注释
$content = preg_replace($preg_1, '', $content);
/* 移除所有单行注释 */
$preg_2 = '#//[^\'\"/]*?\n#u'; // 正则表达式:匹配单行注释
$content = preg_replace($preg_2, "\n", $content);
/* 写入文件 */
file_put_contents($tempFilePath, $content);
}
/* 输出文件 */
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("打包成功!");