Veiking百草园


/ PHP笔录

Phar使用姿势全解析,完美实现php项目打包部署

程序员甲   @Veiking   2021-02-14

Phar使用姿势全解析,完美实现php项目打包部署

摘要:

使用java玩web的大神们都熟悉,当程序开发好打成war包,放入服务器容器,非常方便;PHP5之后,PHP界大神给大家提供了一个功能类似的工具,就是Phar。虽然许多迹象让Phar看起来都像是过时了,但像javaWeb开发那种war包部署的方式,也确实是香,于是就想着研究研究这个Phar,怎么借助Phar,实现类似于javaWeb开发中那种打包部署的功能

缘起

使用java玩web的大神们都熟悉,当程序开发好,准备发布的时候,打成war包,直接放入服务器容器,非常方便;类似,PHP5之后,一众PHP界大神给大家提供了一个功能类似的工具,就是Phar

考虑到PHP本身是不用编译的语言,所以,Phar的打包也不会想java打包那样,生成诸如.class的编译文件,而是将php程序代码直接原样压缩打包,生成一个.phar文件,然后可以像java语言的jar一样调用,也可以像war包一样直接发布运行(这个跟java的war包还是有些不同的,php容器是试图在整个phar文件上,基于设定的访问入口,去整体运行;而java的web容器在运行war包时,实际上是提前做了解压操作的)。

各种机缘,时至今日,从网上的一些教程的情况来看,真正使用Phar去打包的人应该是非常少的,一个是他本身的缺陷,并不能便捷的实现完全类同Java程序打包的那些功能,更重要的是Composer已经事实上成为php项目的代码依赖库标准管理工具,拥有更多相对成熟便捷的方案。除此之外,更多的原因可能是PHP工程师,很多都喜欢直接将代码复制过来复制过去,而不是借助什么工具包,如果非有必要,直接压个.zip文件也就完事儿了,这也就使得Phar的存在比较尴尬。

虽然许多迹象让Phar看起来都像是过时了的老物件,但像java开发那种打war包部署的方式,也确实是香,于是就想着抽空研究研究这个Phar,能不成简单实现一点点类似的功能,毕竟部署项目的时候,来回拷贝源代码这种操作,怎么看都显得不是太成熟太专业(基于PHP的开发,其实也有类的插件,只是笔者的使用场景,不太适合),哈哈哈。

接下来,我们就看看,Phar到底是怎么玩的。

Phar基本操作

关于Phar的打包的使用方法,这里有个例子,感兴趣的可以先看一下:
https://github.com/SegmentFault/phar-sample
基于这个样例项目,我们看看他的基本操作。这里用eclipse随便创建了个vphar的项目,把样例代码考入,可以看到如下的目录结构:


 
参考一般的程序结构,我们大概可以理解:
文件目录app存放的基本是业务逻辑代码;
文件目录lib是用来存放依赖库的,例如三方插件之类的;
文件目录portal我们打开进去看了看,里面只有一个文件index.php文件,明显是作为项目入口的;
文件目录static从字面意思上理解一般都是用来存放静态文件的,例如js、css,图片视频之类的,这种文件一般可能会单独部署;
文件build.php就是我们要看的打包程序了,接下来我们来看看build.php中的脚本代码:

startBuffering();

// 将后缀名相关的文件打包
foreach ($exts as $ext) {
    $phar->buildFromDirectory($dir, '/\.' . $ext . '$/');
}

// 把build.php本身摘除
$phar->delete('build.php');

// 设置入口
$phar->setStub("");
$phar->stopBuffering();

// 打包完成
echo "Finished {$file}\n";

样例代码的注释写的已经很清楚了,我们留意下设置入口这个东西,这段代码的意思是,设置的默认执行入口是.phar包中的portal/index.php文件,然后我们在窗口执行指令:
php build.php

无意外的话,我们既可以在项目根目录下得到一个名为Sample.phar的文件,然后直接运行:
php Sample.phar

既可以看到窗口输出:Hello World!

好了,程序如期运行。梳理下整体的逻辑,我们也可以基本明白这个phar打包的工作逻辑,这里要特别注意下,他这个打包讲究单一入口,这个有点类似于其他语言的main方法,所以在程序结构上要留意下。

(注:使用phar打包,须在php配置文件处修改;phar.readonly = On,分号;去掉,然后把后面的On改为Off,默认只读,设置为Off即可写入,然后才可以操作)

看完这个例子,我们就可以封装一些功能,打包为phar文件,运行起来也很方便。

但是我们不能每次使用的时候,都去运行指令,那该怎么办呢?

其实也很简单,我们在服务容器根目录下,创建个index.php文件,然后加入如下代码:
require __DIR__ . '/Sample.phar';

这样,只要在访问入口index.php文件时,即可去运行Sample.phar文件,然后去执行文件中的默认入口程序,我们可以看到,在浏览器处已输出:


 
好了,到此,我们基本能明白Phar实现打包的基本操作原理了;接下来要尝试实现下我们最初的想法:怎么借助Phar的打包部署,实现类似于java开发中类似于war打包部署的功能

实现Laravel项目打包部署

这里我们要打包的php项目是基于laravel框架的。laravel被业内一些大神戏称为活生生写成了java的php框架,这些玩笑从侧面反映了一个事实,laravel框架从java这种编程语言中借鉴了很多理念性的东西,这也使得laravel比较适合相对复杂业务型的项目。当然,各语言各框架都是在互相借鉴吸收互相参考精进的,laravel框架这熟悉的面孔,也使得java程序员非常容易上手。

我们本次就是想基于laravel框架的项目试试。刚开始的时候,我们发现网上有一些教程,提供了一款基于Phar零配置的便捷打包工具:humbug/box ,看了介绍基本可行,但是尝试之后发现这个工具包太过于陈旧,其要求symfony/console版本在4.4之前,而现在laravel8的symfony/console 版本都已经到了5.2,进行版本回退不太可行,类似的很多版本问题,冲突矛盾无法调和;于是我们就想着干脆就绕开这些个东西,基于phar,自己写脚本来实现一下。

说干就干,项目根目录下,我们也写了一个脚本build.php

startBuffering();

// 忽略文件目录
$INGORE_DIR = '/^(?!.*( deploying|static_files|.git|.setting|.externalToolBuilders)).*$/';
$phar->buildFromDirectory(__DIR__, $INGORE_DIR);

// 忽略文件
$INGORE_FILES = array();//array('.env','.htaccess');
array_push($INGORE_FILES, $PHAR, '.buildpath', '.editorconfig', '.env.bak', '.env.example', '.gitattributes', '.gitignore', '.htaccess.bak', '.project', 'artisan', 'build.php','deploy.php', 'composer.json', 'composer.json.txt', 'composer.lock', 'package.json', 'package-lock.json', 'readme.md', 'webpack.mix.js');
foreach ($INGORE_FILES as $file) {
    if($phar->offsetExists($file)){
        $phar->delete($file);
    }
}

// 压缩方式 Phar::GZ  PHAR::BZ2 等等
$phar->compressFiles(Phar::GZ);
// 如部署服务器不支持phar,则也可打成zip包
//$phar->convertToData(Phar::ZIP);
$phar->stopBuffering();

echo "BUILD IS FINISH!";

// 执行指令
//-> php build.php

除了已有注释说明,这里有几个地方需要特别解释:
我们没有采用那种打成单一入口.phar包,然后用index.php文件来运行的方式,我们试了下,那种方式不现实,JavaWeb程序打的war在运行的时候其实是解压了的,而.phar则是尝试在单一文件上去运行的,代码较少的小功能还可以,如果项目稍微大一点,整个.phar文件就会比较大,这就会导致运行起来非常的卡顿,毕竟加载大文件是也是需要很大开销的;所以我们这里只借助Phar工具做压缩打包,然后部署之后再解压。

注意$INGORE_DIR参数,设置了一些需要忽略的文件目录,这里是一些与项目程序部署不相关文件目录;主要还有静态文件目录(static_files),这个我们单独列出,这些文件普遍较大,压进包里不太合适,一般可能会有独立的处理方案,具体还是看咱们各自的情况。

$INGORE_FILES参数,我们肯定不希望打包的时候把许多跟项目运行不相关的文件压进去;还有一些本地开发测试的环境参数文件,这些就不应该出现在部署包中。

上边build.php文件的脚本,运行之后,即可以将所在项目打包成一个.phar包;由于php容器并没有提供解压操作,我们还需要写个脚本deploy.php:

extractTo($deploy_dir);   // 单纯解压至$deploy_dir

// 部署方式2:直接覆盖解压至项目目录
// $deploy_dir = $HOME;
// $phar->extractTo($deploy_dir, null, true);   

// 删除压缩文件
unlink(__DIR__.'\\'.$PHAR);
// 部署时须放开注释,删除解压缩脚本文件
// unlink(__DIR__.'\deploy.php');

echo "DEPLOY IS FINISH!";

// 执行指令
// php deploy.php

////////// FUNCTION THAT CAN BE IGNORED //////////
// 删除文件目录(含之下目录、文件)
function deldir($path){
    //如果是目录则继续
    if(is_dir($path)){
        //扫描一个文件夹内的所有文件夹和文件并返回数组
        $p = scandir($path);
        //如果 $p 中有两个以上的元素则说明当前 $path 不为空
        if(count($p)>2){
            foreach($p as $val){
                //排除目录中的.和..
                if($val !="." && $val !=".."){
                    //如果是目录则递归子目录,继续操作
                    if(is_dir($path.$val)){
                        //子目录中操作删除文件夹和文件
                        deldir($path.$val.'/');
                    }else{
                        //如果是文件直接删除
                        unlink($path.$val);
                    }
                }
            }
        }
    }
    //删除目录
    return rmdir($path);
}

很显然,deploy.php的作用就是实现包解压的。

当我们将打包好的.phar文件连同脚本文件deploy.php一起放置在服务容器根目录,执行命令: php deploy.php 即可将.phar文件解压至相应的目录,完成部署。
(注:在服务器没做限制的情况下,除了输指令,在浏览器输入:www.youdomain.com/deploy.php 亦可运行)

是不是有点JavaWeb程序war包部署的那个意思了,当然,前提是,目标服务器处是要支持phar的,如不支持,我们就别管deploy.php文件了,直接在本地打包的时候,选择zip包的方式,自己传到服务器去手动玩吧。

到此,两个脚本,一个打包一个解压,就可以让我们的代码干干净净的部署在服务器上,操作起来也便捷了很多,phar是不是也开始香了。

写在最后

本篇内容实现了借鉴JavaWeb程序war包的打包部署形式,利用phar将php(不限于laravel框架)项目,打成独立包,然后再部署至服务器。在脚本的帮助下,部署在服务器的代码纯粹了很多,部署的过程也便捷了很多,写到这里,希望对大家有所帮助。


程序员甲


潜影拾光

南印度洋

古今中外是,天蓝海云先。 around the world, all the same.

扫码转发

二维码
二维码
二维码
二维码
二维码
二维码

博文标签