有很多php脚本工具都是打包成phar形式,使用起来就很方便,那么如何自己做一个呢?也找了很多文档,也遇到很多坑,这里就来总结一下
phar安装
现在直接装yum php-cli包就有phar文件,很方便
可通过phar help查看帮助
重要修改:
php配置文件中
;phar.readonly = On
一定要改成
phar.readonly = Off
phar简单打包
先打包跟简单的单文件
先写一个简单的index.php文件
<?php
echo "HelloWorld\n";
打包
phar pack -f helloworld.phar -c gz -b '#!/usr/bin/php' index.php
参数说明
选中 | 描述 |
---|---|
-f | 指定生成的phar文件名 |
-c | 指定压缩算法 |
-b | 在首行添加内容(可直接执行类似于sh)(单文件时未生效) |
执行
php helloworld.phar
则会输出helloworld
如果文件名不是index.php呢?
[root@localhost test]# phar pack -f helloworld.phar -c gz -b '#!/usr/bin/php' helloworld.php
helloworld.php
[root@localhost test]# php helloworld.phar
PHP Warning: include(phar:///tmp/test/helloworld.phar/index.php): Failed to open stream: phar error: "index.php" is not a file in phar "/tmp/test/helloworld.phar" in /tmp/test/helloworld.phar on line 9
PHP Warning: include(): Failed opening 'phar:///tmp/test/helloworld.phar/index.php' for inclusion (include_path='phar:///tmp/test/helloworld.phar:.:/opt/remi/php83/root/usr/share/pear:/opt/remi/php83/root/usr/share/php:/usr/share/pear:/usr/share/php') in /tmp/test/helloworld.phar on line 9
[root@localhost test]#
打包虽然没报错,但运行找不到index.php文件
通过查看help,看到可以使用-s设置启动文件(使用-s时,后面...文件中可省略该文件,但单文件种情况还是要把单文件加上)
满怀期待的你是不是跃跃欲试了??
root@localhost test]# phar pack -f helloworld.phar -s helloworld.php
PHP Fatal error: Uncaught PharException: illegal stub for phar "/tmp/test/helloworld.phar" (__HALT_COMPILER(); is missing) in phar:///opt/remi/php83/root/usr/bin/phar.phar/pharcommand.inc:534
Stack trace:
#0 phar:///opt/remi/php83/root/usr/bin/phar.phar/pharcommand.inc(534): Phar->setStub()
#1 phar:///opt/remi/php83/root/usr/bin/phar.phar/pharcommand.inc(592): PharCommand->phar_set_stub_begin()
#2 phar:///opt/remi/php83/root/usr/bin/phar.phar/clicommand.inc(87): PharCommand->cli_cmd_run_pack()
#3 /opt/remi/php83/root/usr/bin/phar.phar(52): CLICommand->__construct()
#4 {main}thrown in phar:///opt/remi/php83/root/usr/bin/phar.phar/pharcommand.inc on line 534
[root@localhost test]#
哈哈报错了
注意-s指定的文件行尾必须要加上__HALT_COMPILER();
helloworld.php代码如下
<?php
echo "Hello Wolrd\n";
__HALT_COMPILER();
[root@localhost test]# phar pack -f helloworld.phar -s helloworld.php helloworld.php
helloworld.php
[root@localhost test]# php helloworld.phar
Hello Wolrd
[root@localhost test]#
打包通过运行成功
phar多文件打包
比较单文件打包,坑点比较多,最大的问题是路径问题
先写2个文件
├── helloworld.php
├── lib
│ └── app.php
└── start.php
<?php
//helloworld.php
include __DIR__ . '/lib/app.php';
echo "DIR:" . __DIR__ . "\n";
echo "FILE:" . __FILE__ . "\n";
$app = new app();
$app->show();
echo "HelloWorld\n";
<?php
//lib/app.php
class app {function show() {echo "AppName:" . __CLASS__ . "\n";echo "AppFunc:" . __FUNCTION__ . "\n";echo "AppDir:" . __DIR__ . "\n";echo "AppFile:" . __FILE__ . "\n";}
}
不建议直接改helloworld.php,要改的很多,不是简单的添加__HALT_COMPILER();
不死心的小伙伴可以试试
强烈建议重新写一个启动文件
这里经过不断尝试整理出来比较简单的写法,模板化,后面完全可以全部套用
<?php
//start.php
//工作目录切换到文件所在目录,否则不在文件目录执行会报错
chdir(__DIR__);
//Phar::mapPhar();这样也能通过
Phar::mapPhar("helloworld.phar");
//只能用phar路径,否则不通过
require_once 'phar://helloworld.phar/helloworld.php';
//必须要加,否则stub-set会报错
__HALT_COMPILER();
[root@localhost test]# phar pack -f bin/helloworld.phar -c gz -b '#!/usr/bin/php' -s start.php helloworld.php lib/app.php
helloworld.php
lib/app.php
[root@localhost test]# chmod a+x bin/helloworld.phar
[root@localhost test]# ./bin/helloworld.phar
DIR:phar:///tmp/test/bin/helloworld.phar
FILE:phar:///tmp/test/bin/helloworld.phar/helloworld.php
AppName:app
AppFunc:show
AppDir:phar:///tmp/test/bin/helloworld.phar/lib
AppFile:phar:///tmp/test/bin/helloworld.phar/lib/app.php
HelloWorld
[root@localhost test]#
多文件的-b参数就有效果
特别注意,如果有多级目录不能只写目录名,否则路径就会不对(-s文件可在文件列表中省略)
-s start.php helloworld.php lib/app.php
支持匹配写法
-s start.php *.php lib/*.php
下面这种绝对不行,虽然help中说可以是目录名
[root@localhost test]# phar pack -f bin/helloworld.phar -c gz -b '#!/usr/bin/php' -s start.php helloworld.php lib
helloworld.php
app.php
[root@localhost test]# ./bin/helloworld.phar
PHP Parse error: Unclosed '{' on line 4 in phar:///tmp/test/bin/helloworld.phar/lib/app.php on line 6
[root@localhost test]#
文件直接找不到了,看打包时的文件名就可以看出,文件路径就不对了
Phar其它注意点
从上面的dir输出就可以看到,里面文件或目录都是一种虚拟路径,
实际过程中涉及到的文件及目录操作都需要特殊注意
坑爹示范
//helloworld.php
include __DIR__ . '/lib/app.php';
echo "DIR:" . __DIR__ . "\n";
echo "FILE:" . __FILE__ . "\n";
echo "PharTrue:" . Phar::running(true) . "\n";
echo "PharFalse:" . Phar::running(false) . "\n";
echo "CWD:" . getcwd() . "\n";
$app = new app();
$app->show();
echo "HelloWorld\n";
$filename=__DIR__."/aaa.txt";
file_put_contents($filename, "HelloWorld");
<?php//lib/app.php
class app {function show() {echo "AppName:" . __CLASS__ . "\n";echo "AppFunc:" . __FUNCTION__ . "\n";echo "AppDir:" . __DIR__ . "\n";echo "AppFile:" . __FILE__ . "\n";echo "AppPharTrue:" . Phar::running(true) . "\n";echo "AppPharFalse:" . Phar::running(false) . "\n";echo "AppCWD:" . getcwd() . "\n";$filename = __DIR__ . "/bbb.txt";file_put_contents($filename, "HelloWorld");}}
使用了file_put_contents
不打包应该这样
[root@localhost test]# php helloworld.php
DIR:/tmp/test
FILE:/tmp/test/helloworld.php
PharTrue:
PharFalse:
CWD:/tmp/test
AppName:app
AppFunc:show
AppDir:/tmp/test/lib
AppFile:/tmp/test/lib/app.php
AppPharTrue:
AppPharFalse:
AppCWD:/tmp/test
HelloWorld
打包后第一次看着通过(有的会报错)
[root@localhost test]# phar pack -f helloworld.phar -c gz -b '#!/usr/bin/php' -a helloworld.phar -s start.php helloworld.php lib/app.php
helloworld.php
lib/app.php
[root@localhost test]# phar list helloworld.phar
Unexpected default arguments to command list, check /usr/bin/phar help
[root@localhost test]# phar list -f helloworld.phar
|-phar:///tmp/test/helloworld.phar/helloworld.php
\-phar:///tmp/test/helloworld.phar/lib\-phar:///tmp/test/helloworld.phar/lib/app.php
[root@localhost test]# php helloworld.phar
DIR:phar:///tmp/test/helloworld.phar
FILE:phar:///tmp/test/helloworld.phar/helloworld.php
PharTrue:phar:///tmp/test/helloworld.phar
PharFalse:/tmp/test/helloworld.phar
CWD:/tmp/test
AppName:app
AppFunc:show
AppDir:phar:///tmp/test/helloworld.phar/lib
AppFile:phar:///tmp/test/helloworld.phar/lib/app.php
AppPharTrue:phar:///tmp/test/helloworld.phar
AppPharFalse:/tmp/test/helloworld.phar
AppCWD:/tmp/test
HelloWorld
第一次运行看着正常
[root@localhost test]# phar list -f helloworld.phar
|-phar:///tmp/test/helloworld.phar/aaa.txt
|-phar:///tmp/test/helloworld.phar/helloworld.php
\-phar:///tmp/test/helloworld.phar/lib|-phar:///tmp/test/helloworld.phar/lib/app.php\-phar:///tmp/test/helloworld.phar/lib/bbb.txt
[root@localhost test]# php helloworld.phar
PHP Notice: require_once(): zlib: data error in /tmp/test/helloworld.phar on line 8
PHP Warning: require_once(phar://helloworld.phar/helloworld.php): Failed to open stream: phar error: internal corruption of phar "/tmp/test/helloworld.phar" (actual filesize mismatch on file "helloworld.php") in /tmp/test/helloworld.phar on line 8
PHP Fatal error: Uncaught Error: Failed opening required 'phar://helloworld.phar/helloworld.php' (include_path='.:/opt/remi/php83/root/usr/share/pear:/opt/remi/php83/root/usr/share/php:/usr/share/pear:/usr/share/php') in /tmp/test/helloworld.phar:8
Stack trace:
#0 {main}thrown in /tmp/test/helloworld.phar on line 8
[root@localhost test]#
但在看文件列表,多了aaa.txt和bbb.txt
并且再运行直接报错了
注意Phar::running(true)的返回
解决方式
如果未使用则返回空,可以根据这个来处理文件名
$filename = __DIR__ . "/bbb.txt";if (!empty(Phar::running(true))) {$filename = str_replace(Phar::running(true), getcwd(), $filename);if(!file_exists(dirname($filename))){mkdir(dirname($filename), 0777, true);}}file_put_contents($filename, "HelloWorld");
这样不管打不打包都能正常运行
执行php情况
[root@localhost test]# tree
.
├── aaa.txt
├── helloworld.php
├── lib
│ ├── app.php
│ └── bbb.txt
└── start.php
执行phar情况
[root@localhost bin]# tree
.
├── aaa.txt
├── helloworld.phar
└── lib└── bbb.txt
有更好的方式可以提出来
还有其它参数-i -x 我暂未试出效果,有兴趣的可以自己尝试一下