$nowtime=time();
$pastsec = $nowtime - $_GET["t"];
if($pastsec<600) { exit; //10 分钟更新一次,时间可以自己调整 } ob_start(); //打开缓冲区 include("index.php"); $cOntent= ob_get_contents(); //得到缓冲区的内容 $content .= "\n<script language=Javascript src='http://www.v2ex.com/index/thumb.png' data-original="html.php?t=".$nowtime.""></script>"; //加上调用更新程序的代码
file_put_contents("index.html",$content); if (!function_exists("file_put_contents")) { function file_put_contents($fn,$fs) { $fp=fopen($fn,"w+"); fputs($fp,$fs); fclose($fp);
} } ?>`
以上代码用于生成 typecho 首页 index.html ,运行时提示错误: Warning: Undefined array key "t" in /www/onlineps.top/learn/html.php on line 3
求助高手帮我改写以上代码。
$result = "Hello World" |> strtoupper(...) |> str_shuffle(...) |> trim(...);
同样语法 JS 那边卡了很久,PHP 直接上了。
]]>github 地址 https://github.com/tg111/php-request
直接安装
composer require tg111/php-request
use PhpRequest\PhpRequest; // 简单的 GET 请求 $respOnse= PhpRequest::get('https://httpbin.org/get'); echo $response->text(); // 带参数的 GET 请求 $respOnse= PhpRequest::get('https://httpbin.org/get', [ 'key1' => 'value1', 'key2' => 'value2' ]); // POST 请求 $respOnse= PhpRequest::post('https://httpbin.org/post', [ 'username' => 'user', 'password' => 'pass' ]); // JSON POST 请求 $respOnse= PhpRequest::post('https://httpbin.org/post', [ 'name' => '张三', 'email' => 'zhangsan@example.com' ], [ 'headers' => ['Content-Type' => 'application/json'] ]);
$respOnse= requests_get('https://httpbin.org/get'); $respOnse= requests_post('https://httpbin.org/post', ['key' => 'value']);
$respOnse= PhpRequest::get('https://httpbin.org/cookies', [], [ 'cookies' => [ 'session_id' => 'abc123456789', 'user_preference' => 'dark_mode' ] ]); # cookies $respOnse= PhpRequest::get('https://httpbin.org/cookies', [], [ 'cookies' => [ 'session_id' => 'abc123456789', 'user_preference' => 'dark_mode' ] ]);
会话允许在多个请求之间持久化 Cookie 、请求头和其他配置:
use PhpRequest\PhpRequest; // 创建会话 $session = PhpRequest::session() ->setHeaders([ 'Authorization' => 'Bearer token123', 'Accept' => 'application/json' ]) ->setCookies([ 'session_id' => 'session123' ]) ->setTimeout(60); // 使用会话进行多个请求 $profile = $session->get('/user/profile'); $settings = $session->get('/user/settings'); $updated = $session->post('/user/update', ['name' => '新名称']);
$respOnse= PhpRequest::get('https://httpbin.org/json'); // 获取响应内容 $text = $response->text(); // 原始文本内容 $data = $response->json(); // 解析 JSON 响应 $code = $response->getStatusCode(); // HTTP 状态码 // 检查响应状态 $success = $response->ok(); // 2xx 状态码为 true $isClientError = $response->isClientError(); // 4xx 状态码为 true $isServerError = $response->isServerError(); // 5xx 状态码为 true // 获取头部和元数据 $headers = $response->getHeaders(); $cOntentType= $response->getContentType(); $totalTime = $response->getTotalTime(); $url = $response->getUrl(); // 保存响应到文件 $response->save('/path/to/file.json');
]]>本人是独立开发者,所以更趋向于了解整个开发流程的每个细节,毕竟除了自己,没有任何合作团队。 我认为说再多也没有拿作品说话比较实在。 本人以前就职于香港找换店和餐饮项目开发的公司。带领团队开发。但就是由于本人的观点是非主流观点,导致手下有部分人是不服从的。
由于 php 在主流程序开发界被嗤之以鼻多次,php 官方的开发团队也被强类型语言开发者参与主导。基于我本人自己的观点,感觉开发过于别扭。所以我自己以自己的作品为目标考虑符合自己的技术栈总结了一套开发思路。
本人就职期间原创开发了一个 PHP 开发框架,原名为 mimiphp ,先改名为 lowphp 由于 PHP-FPM 实在性能太差,基于我的本地电脑 AMD5600X wsl 下的 debian12 系统,也只能弄到 500 左右的并发,注意只是 echo 1;简单脚本测试结果。而如果是静态文件,通过 nginx 处理,可以达到 6 万并发。 所以我修改了框架核心,基于 swoole 的 cli 模式,支持了 swoole 协程模式。并且完整加载 mysql 数据和模板引擎渲染后的并发可以达到 5000 左右。我非常满意。
如果单独开启 redis 缓存可以达到 3 万左右并发。如果单独开启 apcu 缓存后可以达到 5 万并发。这实在是给力了。
其实我并不想跟同行过多争论,因为我发现一个现象,就是整个中文社区,其实任何争论都得不到一个比较好的结果。因为可能由于文化的影响,大家都会以自我的认知为中心,方圆几公里之内天下无敌的心态讽刺挖苦别人为目的,实际上根本不能心平气和的讨论任何话题。特别是程序开发行业从架构-语言-框架-甚至包括变量命名方式,都是毫无意义的讽刺挖苦。其实就是浪费时间罢了。对于技术的提升和理解都毫无用处。
我其实在这里发布过两个帖子,一个是关于 php8.3 的讨论。一个是 ffsou 搜索引擎的讨论,一个 2023 年,一个 2024 年。 我是直到刚才才看到 php8.3 讨论的帖子最后一个楼层居然认为我只是一个菜鸟。。这让我干了 20 年的程序员心里真的感到深深伤害。
其实我们这个行业,本身大环境就非常不好,本来我与香港的一个老板是以技术入股的方式合作,又由于前期没有资金聘请其他语言的程序员,而我又是自信能弄好项目上线的老程序员,所以也就是安心的处理代码了。
但我没有想到的是:2020 年到 2024 年底,这个香港老板一直给我反馈的都是无法正常盈利,只能勉强保本。直到 2024 年底,我从其他途径直到了真相,其实他盈利早过了 600 万以上。而且还把自己家人加入进来吃空饷等等原因,我还是决定主动辞职了。
又基于行业限制规则和法律风险的考虑,我重写了我自己的框架,目前是独立开发者身份。然后考虑直接走国际路线,与国外的程序员沟通交流。发现真的行业文化差异实在太大了。
我希望我们作为简体中文的程序员,应该要自信,和睦相处,多与同行技术交流,而不是讽刺挖苦。
当然,国外同样是以作品说话。目前由于金融行业涉及到严格的资金来源追溯问题,找换店已经不行了,但其他方向,特别是 AI 使用上,国外几乎每天都有几十上百个 Saas 项目发布。当然其中也有我一份功劳,赚了点小钱。
所以基于以上的前提,我想说一下我这个 https://www.freetalkhub.com 是参考了 reddit 和 discuz 以及其他社区后,用我新开发的 lowphp 基于 swoole 高并发开发的 php 框架完成。
其中前端采用到了 vue3.0 的 cdn 模式,以及 pjax 伪单页模式。目前收录和用户体验都非常好。
当然,这是一个试运行站点,主要目的还是宣传为主。 但技术栈我是想弱化前端工程化的主流思想。欢迎体验。
而后端 php 一直在更新,我也采用了最新的 php8.4 版本为核心。我希望用我自己的实践和作品,为 php 的开发环境做一个贡献。希望大家参与讨论。前提是站在平等的位置上。不过你讽刺挖苦也无所谓,对于我来说,早已经锻炼出了死猪不怕开水烫的本事。
]]>PHP 源码保护方案有多种,本文说的是对 opcode 进行加密混淆的方案.一般认为,这种方案的加密强度较强,保护程度也较高.
本文调研了两款 PHP 源码加密产品.调研过程中关注两个重点:
为了不对产品本身造成不好的影响, 我们称这两款产品分别为 AAA 和 BBB.
AAA 是国内产品,号称 "最佳 PHP 源代码加密编译器".
BBB 是国外产品,号称 "the most widely trusted PHP protection tool".
先来看 AAA.
首先,我们需要一段 PHP 代码作为被保护对象.这里选取一个对 PDO
类进行简易封装的 Db 类. 完整源码见: Db.php
然后,使用 AAA 试用版 对Db.php
加密, 加密时选择 PHP 版本 8.0,加密完成后下载回来,然后将对应的 AAA_loader_80_nts.so
也下载回来.
php 的 opcache 扩展有个方便的功能,可以把 php 代码的 opcode dump 出来.
$ ~/tmp/php-8.0.30/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x10000' ../Db.php $_main: ; (lines=1, args=0, vars=0, tmps=0) ; (before optimizer) ; /home/hgy/Downloads/php-opcode-test/Db.php:1-97 ; return [] RANGE[0..0] 0000 RETURN int(1) OurBlog_Db::__construct: ; (lines=36, args=0, vars=0, tmps=18) ; (before optimizer) ; /home/hgy/Downloads/php-opcode-test/Db.php:9-17 ; return [] RANGE[0..0] 0000 V1 = NEW 3 string("PDO") 0001 INIT_FCALL 1 96 string("getenv") 0002 SEND_VAL string("DB_HOST") 1 0003 V2 = DO_ICALL 0004 T3 = CONCAT string("mysql:host=") V2 0005 T4 = CONCAT T3 string(";port=") 0006 INIT_FCALL 1 96 string("getenv") 0007 SEND_VAL string("DB_PORT") 1 0008 V5 = DO_ICALL 0009 T6 = CONCAT T4 V5 0010 T7 = CONCAT T6 string(";dbname=") 0011 INIT_FCALL 1 96 string("getenv") 0012 SEND_VAL string("DB_DATABASE") 1 0013 V8 = DO_ICALL 0014 T9 = CONCAT T7 V8 0015 T10 = CONCAT T9 string(";charset=utf8") 0016 SEND_VAL_EX T10 1 // 由于 V2EX 限制主题内容不能超过 20000 个字符,这里删除了余下的 opcode
现在我们拿到了 Db.php
未加密混淆的 opcode.
再来看看 AAA 加密混淆过的 Db-AAA.php
的 opcode 长什么样. 将 AAA_loader_80_nts.so
加到 php.ini
里并配置好.
~/tmp/php-8.0.30/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x1000' Db-AAA.php
什么输出都没有.
可以理解,应该是 AAA_loader_80_nts.so
来接管处理 Db-AAA.php
, opcache 扩展不起作用了.
那还有什么办法能拿到 opcode 吗?可以用 phpdbg
.
$ ~/tmp/php-8.0.30/bin/phpdbg -p* Db-AAA.php function name: (null) L1-97 {main}() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e09cb0 + 1 ops L97 #0 FETCH_DIM_W<-1> 1 NEXT user class: OurBlog_Db 10 methods: __construct, __clone, getInstance, fetchOne, fetchRow, fetchAll, fetchCol, insert, update, __call function name: __construct L9-17 OurBlog_Db::__construct() /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php - 0x719eb1e065a0 + 36 ops L11 #0 NEW<3> "PDO" @0 L2147483647 #1 YIELD<1> "VTM]\\V" L1073741823 #2 PRE_INC "ssipe " L12 #3 DO_FCALL @1 L12 #4 FETCH_DIM_W "mysql:host=" @1 ~2 L12 #5 FETCH_DIM_W ~2 ";port=" ~1 L2147483647 #6 MATCH_ERROR<1> "VTM]\\V" L1073741823 #7 JMPZ_EX "ssihe " L12 #8 DO_FCALL @3 L12 #9 FETCH_DIM_W ~1 @3 ~2 L12 #10 FETCH_DIM_W ~2 ";dbname=" ~1 L2147483647 #11 MATCH_ERROR<1> "VTM]\\V" L1073741823 #12 JMPZ_EX "usmru~ eyu" L12 #13 DO_FCALL @3 L12 #14 FETCH_DIM_W ~1 @3 ~2 L12 #15 FETCH_DIM_W ~2 ";charset=utf8" ~1 // 由于 V2EX 限制主题内容不能超过 20000 个字符,这里删除了余下的 opcode [Script ended normally]
不过 phpdbg
输出的 opcode 没有 opcache 输出的易读,比如最后一个函数OurBlog_Db::__call()
里的call_user_func_array()
没显示完整,只显示了个call_user_func_ar
.
有没有办法让 php-8.0.30 的 phpdbg 输出像 opcache 那种样式的 opcode 呢?
这里只所以要强调 php-8.0.30 的 phpdbg , 是因为 php-8.3 的 phpdbg 输出的 opcode 已经和 opcache 风格统一了.
我们可以对 phpdbg 稍做修改,把 opcache 输出 opcode 的代码用在 phpdbg 里,这样就可以了.
给 phpdbg 新加一个参数-p**
,来调用 opcache 里的 dump 相关代码.
$ ~/tmp/php-8.0.30/bin/phpdbg -p** Db-AAA.php $_main: ; (lines=1, args=0, vars=0, tmps=0) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:1-97 ; return [] 0000 FETCH_DIM_W int(1) NEXT OurBlog_Db::__construct: ; (lines=36, args=0, vars=0, tmps=4, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:9-17 ; return [class] RANGE[--..136834057266072] 0000 V0 = NEW 3 string("PDO") 0001 YIELD (function) string("VTM]\V") 0002 PRE_INC string("ssipe") 0003 V1 = DO_FCALL 0004 T2 = FETCH_DIM_W string("mysql:host=") V1 0005 T1 = FETCH_DIM_W T2 string(";port=") 0006 MATCH_ERROR string("VTM]\V") 0007 JMPZ_EX string("ssihe") 0008 V3 = DO_FCALL 0009 T2 = FETCH_DIM_W T1 V3 0010 T1 = FETCH_DIM_W T2 string(";dbname=") 0011 MATCH_ERROR string("VTM]\V") 0012 JMPZ_EX string("usmru~eyu") 0013 V3 = DO_FCALL 0014 T2 = FETCH_DIM_W T1 V3 0015 T1 = FETCH_DIM_W T2 string(";charset=utf8") 0016 OP_242 T1 0017 BOOL_XOR string("VTM]\V") 0018 FE_RESET_RW string("ssimy ") 0019 V1 = DO_FCALL 0020 SEND_USER V1 2 0021 MATCH_ERROR string("VTM]\V") 0022 OP_244 string("usmfuy kxt") 0023 V1 = DO_FCALL 0024 SEND_USER V1 3 0025 DO_FCALL 0026 FETCH_DIM_W string("pdo") 0027 FETCH_DIM_W V0 NEXT 0028 EXT_STMT T0 string("FTY") 0029 CASE T0 string("AVMw@TS)FGLU") 0030 T0 = FETCH_DIM_W string("PDO") string("ATTR_ERRMODE") 0031 SR T0 0032 T0 = FETCH_DIM_W string("PDO") string("ERRMODE_EXCEPTION") 0033 GET_CLASS T0 0034 DO_FCALL 0035 FETCH_DIM_W null NEXT OurBlog_Db::__clone: ; (lines=1, args=0, vars=0, tmps=0) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:19-20 ; return [undef, ref, class] RANGE[--..136834057268056] 0000 FETCH_DIM_W null NEXT OurBlog_Db::getInstance: ; (lines=9, args=0, vars=0, tmps=2, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:22-28 ; return [] RANGE[--..2207613190024] 0000 T1 = FETCH_DIM_W string("instance") NEXT 0001 T0 = FETCH_DIM_W T1 null 0002 JMPZ T0 0007 0003 V0 = NEW 0 (self) (exception) 0004 DO_FCALL 0005 FETCH_DIM_W string("instance") NEXT 0006 FETCH_DIM_W V0 NEXT 0007 T0 = FETCH_DIM_W string("instance") NEXT 0008 FETCH_DIM_W T0 NEXT OurBlog_Db::fetchOne: ; (lines=13, args=2, vars=3, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:30-35 ; return [ref, class] RANGE[--..6262542] 0000 CV0($Sh40) = RECV 1 0001 CV1($Sh41) = RECV_INIT 2 array(...) 0002 OP_204 T3 string("FTY") 0003 FETCH_DIM_W T3 string("AA\GXRD") 0004 SEND_USER CV0($Sh40) 1 0005 V3 = DO_FCALL 0006 CV2($Sh42) = FETCH_DIM_W V3 NEXT 0007 BIND_LEXICAL (ref) CV2($Sh42) string("TK\TLTD") 0008 SEND_USER CV1($Sh41) 1 0009 DO_FCALL 0010 OP_216 CV2($Sh42) string("TTMU_cN,Q_V") 0011 V3 = DO_FCALL 0012 FETCH_DIM_W V3 NEXT OurBlog_Db::fetchRow: ; (lines=15, args=3, vars=4, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:37-42 ; return [class] RANGE[--..136834057270096] 0000 CV0($Sh43) = RECV 1 0001 CV1($Sh44) = RECV_INIT 2 array(...) 0002 CV2($Sh45) = RECV_INIT 3 zval(type=11) 0003 DECLARE_LAMBDA_FUNCTION T4 string("FTY") 0004 OP_216 T4 string("AA\GXRD") 0005 SEND_USER CV0($Sh43) 1 0006 V4 = DO_FCALL 0007 CV3($Sh46) = FETCH_DIM_W V4 NEXT 0008 SWITCH_STRING CV3($Sh46) 0008 string("TK\TLTD") 0009 SEND_USER CV1($Sh44) 1 0010 DO_FCALL 0011 BW_XOR CV3($Sh46) string("_\LVH") 0012 SEND_USER CV2($Sh45) 1 0013 V4 = DO_FCALL 0014 FETCH_DIM_W V4 NEXT OurBlog_Db::fetchAll: ; (lines=15, args=3, vars=4, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:44-49 ; return [class] RANGE[--..136834057271416] 0000 CV0($Sh47) = RECV 1 0001 CV1($Sh48) = RECV_INIT 2 array(...) 0002 CV2($Sh49) = RECV_INIT 3 zval(type=11) 0003 OP_204 T4 string("FTY") 0004 OP_246 T4 string("AA\GXRD") 0005 SEND_USER CV0($Sh47) 1 0006 V4 = DO_FCALL 0007 CV3($Sh410) = FETCH_DIM_W V4 NEXT 0008 OP_220 CV3($Sh410) string("TK\TLTD") 0009 SEND_USER CV1($Sh48) 1 0010 DO_FCALL 0011 YIELD_FROM CV3($Sh410) string("WPMT^aM,") 0012 SEND_USER CV2($Sh49) 1 0013 V4 = DO_FCALL 0014 FETCH_DIM_W V4 NEXT OurBlog_Db::fetchCol: ; (lines=15, args=2, vars=3, tmps=1, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:51-56 ; return [ref, class] RANGE[--..136834057272704] 0000 CV0($Sh411) = RECV 1 0001 CV1($Sh412) = RECV_INIT 2 array(...) 0002 OP_222 T3 string("FTY") 0003 FETCH_DIM_W T3 string("AA\GXRD") 0004 SEND_USER CV0($Sh411) 1 0005 V3 = DO_FCALL 0006 CV2($Sh413) = FETCH_DIM_W V3 NEXT 0007 CASE CV2($Sh413) string("TK\TLTD") 0008 SEND_USER CV1($Sh412) 1 0009 DO_FCALL 0010 BIND_LEXICAL (ref) CV2($Sh413) string("WPMT^aM,") 0011 T3 = FETCH_DIM_W string("PDO") string("FETCH_COLUMN") 0012 OP_231 T3 0013 V3 = DO_FCALL 0014 FETCH_DIM_W V3 NEXT OurBlog_Db::insert: ; (lines=55, args=2, vars=7, tmps=5, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:58-74 ; return [class] RANGE[--..136834057274120] 0000 CV0($Sh414) = RECV 1 0001 CV1($Sh415) = RECV 2 0002 JMPNZ CV1($Sh415) 0011 0003 CONCAT T7 string("FTY") 0004 SWITCH_STRING T7 0004 string("RA][") 0005 T8 = DECLARE_ANON_CLASS string("INSERT INTO `") 0006 T8 = DECLARE_ANON_CLASS T8 CV0($Sh414) 0007 T7 = FETCH_DIM_W T8 string("` VALUES (NULL)") 0008 OP_228 T7 0009 DO_FCALL 0010 FETCH_DIM_W null NEXT 0011 CV2($Sh416) = FETCH_DIM_W array(...) NEXT 0012 DEFINED string("PKKVIJ%]A") 0013 SEND_USER CV1($Sh415) 1 0014 V8 = DO_FCALL 0015 V7 = FETCH_DIM_W V8 NEXT 0016 FETCH_DIM_W V7 CV3($Sh417) 0017 T9 = DECLARE_ANON_CLASS string("`") 0018 T9 = DECLARE_ANON_CLASS T9 CV3($Sh417) 0019 T8 = FETCH_DIM_W T9 string("`") 0020 FETCH_DIM_W CV2($Sh416) NEXT 0021 FETCH_DIM_W T8 NEXT 0022 FETCH_DIM_W NEXT 0023 FE_FREE V7 0024 BW_NOT string("X^I[VDD") 0025 FETCH_OBJ_IS THIS string("") 0026 SEND_USER CV2($Sh416) 2 0027 V7 = DO_FCALL 0028 FETCH_DIM_W CV2($Sh416) V7 0029 MUL string("BMKhBEQ%EF") ")30 FE_RESET_RW string(" 0031 T8 = FETCH_DIM_W CV1($Sh415) NEXT 0032 T7 = FETCH_DIM_W T8 int(1) 0033 SEND_USER T7 2 0034 V8 = DO_FCALL 0035 CV4($Sh418) = FETCH_DIM_W V8 string("?") 0036 T8 = DECLARE_ANON_CLASS string("INSERT INTO `") 0037 T8 = DECLARE_ANON_CLASS T8 CV0($Sh414) 0038 T8 = DECLARE_ANON_CLASS T8 string("` (") 0039 T8 = DECLARE_ANON_CLASS T8 CV2($Sh416) 0040 T8 = DECLARE_ANON_CLASS T8 string(") VALUES (") 0041 T8 = DECLARE_ANON_CLASS T8 CV4($Sh418) 0042 CV5($Sh419) = FETCH_DIM_W T8 string(")") 0043 CONCAT T7 string("FTY") 0044 BIND_LEXICAL (ref) T7 string("AA\GXRD") 0045 SEND_USER CV5($Sh419) 1 0046 V7 = DO_FCALL 0047 CV6($Sh420) = FETCH_DIM_W V7 NEXT 0048 OP_216 CV6($Sh420) string("TK\TLTD") 0049 MATCH_ERROR string("SAKWMW!HG]C") 0050 SEND_USER CV1($Sh415) 1 0051 V7 = DO_FCALL 0052 SEND_USER V7 1 0053 DO_FCALL 0054 FETCH_DIM_W null NEXT OurBlog_Db::update: ; (lines=44, args=3, vars=7, tmps=4, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:76-90 ; return [class] RANGE[--..136834057277472] 0000 CV0($Sh421) = RECV 1 0001 CV1($Sh422) = RECV 2 0002 CV2($Sh423) = RECV_INIT 3 string("1") 0003 JMPNZ CV1($Sh422) 0008 0004 V7 = NEW 1 string("Exception") 0005 OP_226 string("update with empty row is not allowed!") 0006 DO_FCALL 0007 FETCH_DIM_W V7 NEXT 0008 CV3($Sh424) = FETCH_DIM_W array(...) NEXT 0009 MUL string("PKKVIJ%]A") 0010 SEND_USER CV1($Sh422) 1 0011 V8 = DO_FCALL 0012 V7 = FETCH_DIM_W V8 NEXT 0013 FETCH_DIM_W V7 CV4($Sh425) 0014 T9 = DECLARE_ANON_CLASS string("`") 0015 T9 = DECLARE_ANON_CLASS T9 CV4($Sh425) 0016 T8 = FETCH_DIM_W T9 string("` = ?") 0017 FETCH_DIM_W CV3($Sh424) NEXT 0018 FETCH_DIM_W T8 NEXT 0019 FETCH_DIM_W NEXT 0020 FE_FREE V7 0021 FETCH_FUNC_ARG (global) string("X^I[VDD") 0022 OP_205 string("") 0023 SEND_USER CV3($Sh424) 2 0024 V7 = DO_FCALL 0025 FETCH_DIM_W CV3($Sh424) V7 0026 T8 = DECLARE_ANON_CLASS string("UPDATE `") 0027 T8 = DECLARE_ANON_CLASS T8 CV0($Sh421) 0028 T8 = DECLARE_ANON_CLASS T8 string("` SET ") 0029 T8 = DECLARE_ANON_CLASS T8 CV3($Sh424) 0030 T8 = DECLARE_ANON_CLASS T8 string(" WHERE ") 0031 CV5($Sh426) = FETCH_DIM_W T8 CV2($Sh423) 0032 POST_INC T7 string("FTY") 0033 BW_XOR T7 string("AA\GXRD") 0034 SEND_USER CV5($Sh426) 1 0035 V7 = DO_FCALL 0036 CV6($Sh427) = FETCH_DIM_W V7 NEXT 0037 OP_220 CV6($Sh427) string("TK\TLTD") 0038 FETCH_FUNC_ARG string("SAKWMW!HG]C") 0039 SEND_USER CV1($Sh422) 1 0040 V7 = DO_FCALL 0041 SEND_USER V7 1 0042 DO_FCALL 0043 FETCH_DIM_W null NEXT OurBlog_Db::__call: ; (lines=10, args=2, vars=2, tmps=2, dynamic, irreducable, extended_stmt, extended_fcall) ; /home/hgy/Downloads/php-opcode-test/AAA/Db-AAA.php:92-95 ; return [!ref, class] RANGE[--..109071675059458] 0000 CV0($Sh428) = RECV 1 0001 CV1($Sh429) = RECV 2 0002 DECLARE_LAMBDA_FUNCTION T3 string("FTY") 0003 T2 = FETCH_DIM_W T3 NEXT 0004 T2 = DECLARE_ANON_CLASS CV0($Sh428) 0005 NEW 0 string("call_user_func_array") T2 0006 SEND_UNPACK CV1($Sh429) 0007 FETCH_DIM_W NEXT 0008 V2 = DO_FCALL 0009 FETCH_DIM_W V2 NEXT [Script ended normally]
好,现在我们拿到了 AAA 加密混淆过的 opcode.
接下来就要把这些 opcode 给反编译成 PHP 代码. 这可不好弄. 不过好在有 AI 大模型,是时候展现 AI 真正的实力了!
以下是 Google Gemini 反编译的结果:
@see https://g.co/gemini/share/148782890130
大家可以自行对比一下,反正我是被震惊到了!
也有可能 Db.php
的代码较为常见,被 AI 蒙对了.
腾讯元宝 DeepSeek-R1 反编译的结果如下:
@see https://yuanbao.tencent.com/bot/app/share/chat/KUiqoTNjZalJ
AAA 的调研我们先到这里.
AAA 留给我们的问题是, opcode 到底能不能程序化地反编译成 PHP 代码.
去 BBB 的网站上把 BBB 的 encoder 试用版 和 loader 都下载回来.
BBB 的 encoder 没有 php-8.0 版本的, 那我们就选最高可用版本 php-8.3 的.
同样对 Db.php
进行加密, 得到加密后的文件 Db-BBB.php
./BBB_encoder_evaluation/BBB_encoder.sh -C -x86-64 -83 ../Db.php -o Db-BBB.php
接下来我们用同样的办法尝试拿到 Db-BBB.php
的 opcode.
将 BBB_loader_lin_8.3.so
加到 php.ini 里配置好.
先用 opcache 尝试一下:
$ ~/tmp/php-8.3.21/bin/php -d 'opcache.enable_cli=1' -d 'opcache.opt_debug_level=0x1000' Db-BBB.php
没有任何输出.
再用 phpdbg 尝试一下:
$ ~/tmp/php-8.3.21/bin/phpdbg -p* Db-BBB.php Segmentation fault (core dumped)
直接 segfault 了.
我们使用 gdb 来调试一下.
$ gdb ~/tmp/php-8.3.21/bin/phpdbg (gdb) b phpdbg_compile_file (gdb) r -p* ../Db.php (gdb) n (gdb) 250 ret = PHPDBG_G(compile_file)(file, type); (gdb) 251 if (ret == NULL) { (gdb) set print pretty on (gdb) p *ret $1 = { type = 2 '\002', arg_flags = "\000\000", fn_flags = 100663296, function_name = 0x0, scope = 0x0, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, attributes = 0x0, run_time_cache__ptr = 0x0, T = 0, cache_size = 0, last_var = 0, last = 1, opcodes = 0x7ffff5002450, static_variables_ptr__ptr = 0x0, static_variables = 0x0, vars = 0x0, refcount = 0x7ffff5004008, last_live_range = 0, last_try_catch = 0, live_range = 0x0, try_catch_array = 0x0, filename = 0x7ffff505e3c0, line_start = 1, line_end = 97, doc_comment = 0x0, last_literal = 1, num_dynamic_func_defs = 0, literals = 0x7ffff5002470, dynamic_func_defs = 0x0, reserved = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} } (gdb) c Continuing. ... [Script ended normally] [Inferior 1 (process 187677) exited normally] (gdb) r -p* Db-BBB.php (gdb) n (gdb) 250 ret = PHPDBG_G(compile_file)(file, type); (gdb) 251 if (ret == NULL) { (gdb) p *ret $2 = { type = 2 '\002', arg_flags = "\000\000", fn_flags = 100663296, function_name = 0x0, scope = 0x0, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, attributes = 0x0, run_time_cache__ptr = 0x7ffff5004030, T = 1, cache_size = 0, last_var = 0, last = 0, opcodes = 0x1, static_variables_ptr__ptr = 0x0, static_variables = 0x0, vars = 0x0, refcount = 0x7ffff5004020, last_live_range = 0, last_try_catch = 0, live_range = 0x0, try_catch_array = 0x0, filename = 0x0, line_start = 1, line_end = 0, doc_comment = 0x0, last_literal = 0, num_dynamic_func_defs = 0, literals = 0x0, dynamic_func_defs = 0x0, reserved = {0x0, 0x0, 0x0, 0x7ffff5081460, 0x0, 0x0} } (gdb) quit
对比两次 p *ret
不难发现, 未加密的 Db.php
:
opcodes = 0x7ffff5002450, reserved = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
已加密的 Db-BBB.php
:
opcodes = 0x1, reserved = {0x0, 0x0, 0x0, 0x7ffff5081460, 0x0, 0x0}
0x1
显然不是一个有效的内存地址,现在 opcode 在哪儿,不好找了.
再次 gdb 关注一下 zend_compile_file
和 zend_execute_ex
:
$ gdb ~/tmp/php-8.3.21/bin/php (gdb) watch zend_compile_file (gdb) watch zend_execute_ex (gdb) r Db-BBB.php ... (gdb) Continuing. Hardware watchpoint 1: zend_compile_file Old value = (zend_op_array *(*)(zend_file_handle *, int)) 0x555555786ae0 <phar_compile_file> New value = (zend_op_array *(*)(zend_file_handle *, int)) 0x7ffff40e5041 0x00007ffff40571d4 in ?? () from /home/hgy/Downloads/php-opcode-test/BBB/BBB/BBB_loader_lin_8.3.so (gdb) Continuing. Hardware watchpoint 2: zend_execute_ex Old value = (void (*)(zend_execute_data *)) 0x55555596adf0 <execute_ex> New value = (void (*)(zend_execute_data *)) 0x7ffff40f2784 0x00007ffff40571de in ?? () from /home/hgy/Downloads/php-opcode-test/BBB/BBB/BBB_loader_lin_8.3.so
可以看到 BBB_loader_lin_8.3.so
既接管了 zend_compile_file
, 又接管了 zend_execute_ex
. 这样 opcodes 就成了个黑盒子, 我们既不知道在哪儿,也不知道内容.
这怎么办呢? 是不是说 BBB 这个产品加密强度非常强,值得信赖呢?
别急,网上搜索一下. 很快就找到了这个 https://dezender.xyz/
在 DECODERS 菜单里,就有 BBB PHP 8.3, 可以在线试用,只不过只能 decode 10 行,我们试一下.
成功解密.
<?php /* * @ https://dezender.xyz - BBB Decoder Online * @ Decoder version: 3.0.0 * @ Release: 2025/04/09 */ class OurBlog_Db { protected static $instance = null; protected $pdo = null; protected function __construct(){ $this->pdo = new PDO("mysql:host=" . getenv("DB_HOST") . ";port=" . getenv("DB_PORT") . ";dbname=" . getenv("DB_DATABASE") . ";charset=utf8", getenv("DB_USER"), getenv("DB_PASSWORD")); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } private function __clone(){ } // This is the demo version. Demo version decode 10 lines only.
简直太强了!!!
/** * 项目 socket 连接时触发(一次) * 当客户端与 Workerman 建立连接时(TCP 三次握手完成后)触发的回调函数 */ public static function onConnect($client_id) { // 定义连接函数 $cOnnectWs= function () { $ws = new AsyncTcpConnection('ws://dashscope.aliyuncs.com/api-ws/v1/inference'); $ws->headers = [ "Authorization" => "Bearer " . getenv("DASHSCOPE_API_KEY"), ]; $ws->transport = "ssl"; // 连接成功回调 $ws->OnConnect= function ($ws) { self::$aliWs = $ws; echo "connected success:" . $ws->id . "\n"; }; // 当收到消息时 $ws->OnMessage= function ($ws, $data) { var_dump("ali 返回消息", $data); $msg = json_decode($data, true); $channel = self::$sendWsChan; Coroutine::create( function () use ($channel, $msg) { $channel->push($msg); }, ); }; // 连接关闭时进行重连 $ws->OnClose= function ($ws) { echo "连接关闭,尝试重新连接...\n"; self::$aliWs = null; $ws->reConnect(1); }; $ws->OnError= function ($ws) { echo "错误输出" . $ws->error; }; $ws->connect(); }; //定时器触发 if (self::$aliWs) { var_dump("发送事件"); self::$aliWs->send(json_encode($data)); }
初次建立长连接可以发送成功,但是如果阿里云长连接断线重连几次后,发送事件,在 onMessage 里边没有响应。不明白为什么出现这种情况。var_dump("发送事件"); 可以打印数据。但是 onMessage 不存在数据。
]]>package
都是用私有包分发的,用户使用私有包的授权码获取更新包。当然这个项目的主要功能是分发包,而不是保护包的源代码。 感兴趣的可以关注下 😂 Packvault 开源地址
做一些 PHP
项目 然后给用户做分发还是挺有用的
回头重新看了 php 协程 发现 php 协程实在太垃圾了 好像有个官方文档 提供了大量示例 协程也可以取消 但它是把所有 job 放入一个队列 取消就是从队列删除 实在太垃圾了 跟 python asyncio 比都不能比 python asyncio 可以直接把函数创建为 task 然后超时的话 在主控制函数里直接 task.cancel()
php 的协程代码也是看起来太啰嗦 一团糟
这几年 php 的份额不断被 js go 蚕食 php 要推出 jit 主要作者是鸟哥 其实鸟哥是国内 php 卷王 必须要维持自己的生存 不断给 php 进化 不然 php 被抛弃了 被扫进历史垃圾堆了 他的日子也要不好过了
]]>用户审核通过,然后给他的邀请人加优惠价。
伪代码 foreach(users as user){ //通过审核(事务保证) // 给邀请人加优惠卷(事务保证) }
现在出现的问题是用户加优惠价这个过程,对用户信息更新优惠卷总数失败。
给用户加优惠卷有 2 个插入操作,一个更新操作。
刚才进行批量操作,执行了 20 个用户,其中几个 update 操作失败。所以对这个问题没想明白,mysql rr 级别。
]]>如果想继续待下去,是学前端还是运维呢 或者卷其它,目前基本的运维会,k8s 懂一些
]]>本以为这个版本之后,下个版本会直升 PHP 9.0 ,带来一些新的东西,没想到还是小版本 PHP 8.5 ,虽然 PHP 正在没落,但还是希望她能够越来越完善。
]]>在 Xserver 中优雅地实现了这个功能。只需要鼠标点击一下即可快速切换镜像,即时生效。
Xserver 内置了阿里云、华为云、腾讯云和默认镜像,你可以在这些镜像之间随意快速切换。
]]>if($request->input('lang')){ app()->setLocale($request->input('lang')); }
return $next($request);
首次设置生效,切换页面不带'lang'参的时候在控制器里用 app()->getLocale()就还是只能拿到默认语言。使用 session 也是一样。检查了中间件的顺序,应该是没什么问题,也没有其他的地方有设置语言操作。求 debug 思路
]]>I'm excited to announce that Laravel has raised a $57M Series A in partnership with Accel. I believe that Laravel is the most productive way to build full-stack web applications, and Laravel Cloud will be the platform for shipping those applications that this community deserves.
首轮融资 5700 万美金。
可以用上免费的 Laravel Cloud 产品咯
]]>PHP
的Laravel
框架(或者ThinkPHP
)是真的好用啊,很多功能开箱即用,为什么现在的Web
框架反而不这么做了呢? 比如Go
的Gin
,Node
的express
,Python
的Flask
,都是定义路由、返回JSON
、模板渲染、静态文件服务这几个基础功能,想要别的需求还得自己装,自己装数据库驱动、搞注册登录、搞邮件发送什么的,更别说结合前端的Vue
、React
、Tailwind CSS
这些了,不会前端的搞配置都要搞半天。
反观Laravel
系列,上面的功能要么自带,要么装个库运行下命令就自动全配置好了,简直不要太方便!
狠狠地被惊艳到了!
]]>vhost
够用了。而且要容器化 vhost
也很容易这么好的 serverless 是哪家?请看本贴所属节点 😆😆😆😆😆
同步发表于我的 blog
]]>从一个新装好的 Ubuntu 24.04 Desktop 的虚拟机开始:
composer + PHP 运行项目没问题
去掉 composer 运行项目没问题
PHP 跑通 PHPUnit 测试
BPC 编译跑通 PHPUnit 测试
PHP 环境下项目各项功能测试没问题
BPC 编译运行起来项目各项功能测试没问题
[ PHP 编译器 BPC 编译 ThinkPHP8 + PHPUnit 测试] https://www.bilibili.com/video/BV1Laece6Epr/?share_source=copy_web&vd_source=fb7701afa4e3c4faaffabbe49ab11ac2
]]>BPC 还内置软件授权机制,最终可实现源码保护、软件授权、二进制打包三合一!
彻底解决 PHP 项目的交付问题.
写在前面:
之前就有网友提过能不能编译 ThinkPHP,我也翻看过几次 ThinkPHP 的代码,虽然 ThinkPHP 带有测试用例,但是一眼看去很少,不清楚能覆盖到多少功能点.
再加上我自己从没写过 ThinkPHP 的项目,对 ThinkPHP 很陌生,所以一直没有尝试编译.
由于 php 的动态性和 bpc 与 php 的高兼容特点,bpc 编译通过不代表就没问题了,如果有测试用例保障的话,通过运行测试用例可以验证编译后的二进制可执行文件是否与原来的 php 等同.
之前写的一本关于 PHPUnit 的电子书《 PHPUnit in Action --- The Easy Way 》里有一个博客项目 OurBlog,虽然功能很简单,但基本的 CURD 都涉及到了,测试也非常完整.
于是就想着把 OurBlog 迁移到 ThinkPHP 试一下,由于有测试保障,这个迁移应该比较好做.
一番折腾之后,迁移成功了! 源码见: bpc-thinkphp8-ourblog
在使用 composer 创建 ThinkPHP 项目时,可以看到一个 ThinkPHP8 项目有以下依赖:
league/mime-type-detection (1.15.0) league/flysystem (2.5.0) psr/container (2.0.2) psr/http-message (1.1) psr/simple-cache (3.0.0) psr/log (3.0.0) symfony/polyfill-mbstring (v1.29.0) symfony/var-dumper (v7.1.1) topthink/think-helper (v3.1.6) topthink/think-orm (v3.0.20) topthink/framework (v8.0.3) topthink/think-filesystem (v2.0.2) topthink/think-trace (v1.6)
进一步地,跑通 OurBlog 测试用例, 只需要搞定 3 个依赖就可以了:
最后,ourblog 的前端界面能正常运行,不需要搞定所有依赖,只需要搞定下边 4 个就行了:
由于 topthink/framework 和 think-orm 里都包含了 think\Facade
和 think\Exception
, 需要把 think-orm/stubs
独立出来, 再加上 ourblog 项目本身,一共 9 个 repo, 见这里.
要想一行代码不动就能编译成功,几乎是不可能的.
代码调整主要集中在 3 个方面:
is_file/is_dir/file_exists/glob
来判断,要换用 bpc 自己的专有函数.代码调整的细节可以查看每个 repo 的 commit 历史.
tp8-ourblog-althttpd-ubuntu-24.04-amd64
详见: bpc-thinkphp8-ourblog release v0.1
后边可以出个视频来演示一下整个编译运行的过程.
]]>Dux Next 是一款简单、易用、易开发、独立部署的应用式管理系统,该系统采用各类成熟开源的三方库进行集成封装,免去后续维护的大量工作,自带开箱即用的 CMS 模块,适合长期开发和维护。配合不同的应用可以很轻松的实现您想要的站点,诸如公司网站、博客、CRM 、ERP 、商城等等。
后端使用 SlimPHP 框架作为基础,同时整合了 Eloquent ORM 作为主要的数据驱动。同时还集成了各大 Psr 规范化的主流组件,例如 PSR-7 、PSR-11 、PSR-15 等,以确保框架具有高度的可扩展性和互操作性,该封装框架名为 Duxlite 。
前端使用 Refine 无头框架与 TDesign 组件库进行结合,快速开发美观优雅的操作界面。同时提供丰富的 Api 接口,可轻松将数据分享给三方或前台业务,该封装框架名为 Duxrefine 。
各类 php 和全栈开发者,开箱即用快速上线项目
MIT
https://github.com/duxweb/duxcms 麻烦给个 star
]]>pgsql
,tap
用的是shivammathur/php/php@7.4
php -m
包含了基本的扩展,pdo_pgsql
和pgsql
扩展直接编译进了内核 ➜ Formula git:(master) php -m [PHP Modules] bcmath bz2 calendar Core ctype curl date dba dom exif FFI fileinfo filter ftp gd gettext gmp hash iconv intl json ldap libxml mbstring mysqli mysqlnd odbc openssl pcntl pcre PDO pdo_dblib pdo_mysql PDO_ODBC pdo_pgsql pdo_sqlite pgsql Phar phpdbg_webhelper posix pspell readline Reflection session shmop SimpleXML soap sockets sodium SPL sqlite3 standard sysvmsg sysvsem sysvshm tidy tokenizer xml xmlreader xmlrpc xmlwriter xsl Zend OPcache zip zlib [Zend Modules] Zend OPcache
但是
new PDO()
后直接提示
WARNING: [pool www] child 69580 exited with code 124 after 795.206755 seconds from start
有人遇到过吗
]]>行为说明 当第一个参数( haystack )为空字符串时: strpos() 函数会返回 0 ,表示子字符串( needle )在空字符串的起始位置找到(位置为 0 )。这是因为在 PHP 中,空字符串的起始位置被视为 0 。 示例:
$haystack = ""; $needle = "a"; $position = strpos($haystack, $needle); var_dump($position); // 输出 int(0)
我在我本地运行得到的结果确是:
$address = ""; $word = 'a'; // $rs = strpos($address, $word); var_dump($rs); //输出 bool(false)
这是 PHP 版本的问题还是 gpt 回答错了?
]]>https://www.网址.com/edu
在 pc 端正常访问,但在移动端就会被跳转到其他网站上。 如修改路由为/edu123
也会跳转,修改为/ed
则不会。初步判断关键字为edu
。 dns
污染https
,排除nginx
php
清除每个站点下的 pass.php 文件,以及相同时间被创建的一些文件。检查 php.ini 文件以及 so 文件是否被修改。没有发现可疑配置后,重启 php 服务再次访问。 结果还是一样会跳转到其他网站。。。
查看 php 慢日志时,发现请求网页时有执行file_get_contents
函数,于是循着文件路径查看,找到了罪魁祸首,项目composer
下的autoload_real.php
被植入了一行代码,删除掉后网址恢复正常。
请问这是利用了 composer 的漏洞吗? composer 的版本是 2.3.7? 然后被植入 pass.php 的文件是不是宝塔的漏洞导致被上传的?因为我看到这些文件都是 www 宝塔用户上传的。
]]>报错内容为:
Your requirements could not be resolved to an installable set of packages. Problem 1 - laravel/framework[v11.9.0, v11.9.1, v11.9.2, 11.x-dev] require fruitcake/php-cors ^1.3 -> found fruitcake/php-cors[dev-feat-setOptions, dev-master, dev-main, dev-test-8.2, v0.1.0, v0.1.1, v0.1.2, v1.0-alpha1, v1.0-alpha2, v1.0-beta1, v1.0.0, v1.0.1, v1.1.0, 1.1.x-dev (alias of dev-main), v1.2.0, 1.2.x-dev (alias of dev-master)] but it does not match the constraint. - Root composer.json requires laravel/framework ^11.9 -> satisfiable by laravel/framework[v11.9.0, v11.9.1, v11.9.2, 11.x-dev].
经过对比 某云源 fruitcake/php-cors.json 和packagist fruitcake/php-cors.json,实际上某云的镜像源并未更新
我通过两种方式尝试解决
不知各位是如何拉取 composer 包的,现在这些源都™的给限制了,开发都没脾气了。。。
]]>谈谈云招 OurATS 为什么不把开发语言从 PHP 转成 go/java/.net,而是搞了个 PHP 编译器 BPC 来实现本地部署
简言之,云招 OurATS 是一个使用 PHP 语言开发的招聘管理系统.
经常看到有网友接手 PHP/Python 等动态语言开发的项目后,不管是加功能还是调 bug,都痛苦不不堪,直呼"动态一时爽,重构火葬场".
确实,我本人在参与开发云招 OurATS 的前两年里也有类似的经历.
当时面临最大的问题是,不敢轻易加需求.
为什么?
因为经过 2 年的开发,云招 OurATS 的代码库已经稍有规模.每添加一个需求,是否会影响到其它功能模块,都需要好好测试才放心.
可是招聘系统的功能模块多,相互关联错综复杂,要测试的地方太多了,就一个字,累!
当时敏捷开发、TDD 是非常流行的,尤其是 TDD 很明显可以解决我们面临的测试问题,所以当时狠花了一些时间研究怎么搞 TDD.
也去聆听过某知名公司组织的敏捷开发分享交流会,发现方法论比较多,落地实战几乎没有.
于是静下心来,自己搞.
核心关键点:
测试用例不好写
那是被测试代码不容易测,需要重组代码结构,让测试用例好写
要高覆盖率吗?
覆盖率只是一个指标/手段,不是目的.
目的是建立对被测试代码的信心,写个1+1=2
根本不需要测试,因为我们有信心不会出错.
核心逻辑覆盖到,在担心出错的地方多覆盖,在有信心不出错的地方少覆盖.
摆正心态,写测试用例肯定会多花点时间,但这是为了不去体验火葬场
云招 OurATS 至今已经历了三次大的升级:
每次升级,都是先跑通测试用例,这时会有一些 SQL 和 PHP 的兼容性调整,但都很轻松地搞定了.
升级后系统运行地也很平稳,利益于 PHP 近些年来的性能提升,每次升级都还能节省几台服务器.
在 2023 年初,我们使用 PHP 编译器 BPC 成功编译了云招 OurATS.
BPC 完全脱离了 PHP 解释器,每一个 PHP 的语法、扩展函数都重新实现了一遍,这当中如果没有测试做保障,那根本就是不可能完成的任务.
好在 PHP 自身有完善的 phpt 测试用例,BPC 首先通过了这一层测试.
OurATS 相关的每个项目/模块也都有写好的 PHPUnit 测试用例,BPC 也通过了这一关测试.
测试都过了,我们很有信心 BPC 编译后的 OurATS 等价于 PHP 解释执行的 OurATS.
如此大规模的升级都轻松搞定了,日常的小升级更不在话下.
这十多年来,每年我们的代码库都会有相当规模的变化,但这十多年里,我们没再体验过火葬场,我们也非常自信云招 OurATS 和"屎山"不沾边.
今天就来说说为什么?
最初决定要开发 BPC 是为了想要本地部署云招 OurATS 的一个核心组件 简历解析器 bob-parser.
bob-parser 是用 PHP 开发的,而 PHP 的源码加密方案没有找到一个 100%可靠的,并且还想解决软件授权问题.
有网友一提到源码保护什么的,老是会说你的代码是有多好,多有价值,给我我也不看,屎山一堆.
这个问题我们后边再讨论.
但云招的做事风格大致就是这样,想要解决一个问题时,就会尽可能地想把这个问题解决好.
开发了 BPC 一段时间后,发现实际上不只能解决 php cli 程序的编译,php web 项目通过编译成动态链接库当作 module 嵌入 apache 就好了,再进一步,引入了 althttpd, apache 也不需要了.
云招 OurATS 是一个招聘管理系统, ATS 是 Applicant Tracking System 的缩写.
非这个领域的人一开始往往会把 ATS 和招聘渠道(Jobboard)弄混.
招聘渠道是指 Boss 直聘/智联招聘/51job 等面向求职者的网站.
企业从招聘渠道获取到简历后,或者说候选人把简历投递给企业后,下一步进行 简历筛选/征求用人部门意见/安排面试/Offer 审批/Offer 发放... 等工作时需要的 申请追踪系统 就是 ATS.
当然现在的招聘渠道企业后台可能也有一部分 ATS 的功能.
云招 OurATS 没怎么搞市场推广,所以虽然我们从 2010 年就开始做了,很多网友可能没听说过.
这里列几个大家可能听过/用过的招聘管理系统.
有些网友认为开发一套招聘管理系统没什么难的,找几个人搞个半年还能搞不出来?
我们来看看实际案例.
北森在 2019 重构了它的招聘管理系统,在其官网发布的文章中这样说:
2019 年,北森基于 Nature Design3.0“高效、愉悦、温暖”的设计理念,历时 3 年,斥资 2 亿人民币,重塑新一代体验优先的招聘管理系统。
文章链接: https://www.beisen.com/res/848.html
显然,北森的这次重构应该没有更换技术栈,从其 招聘的岗位 来看,开发语言应该是 java/.net.
在不更换开发语言的情况下,重做一个招聘管理系统的成本是 3 年 + 2 亿人民币.
如果换语言的,成本恐怕不只这么多了.
那么这个历时 3 年,斥资 2 亿人民币,重塑新一代的招聘系统有惊艳了市场吗?看看北森在港股的表现就知道了.
在脉脉上经常看到 Moka 比北森好的评价,可是在脉脉上 Moka 比北森裁员裁和还狠.
如果还有网友不信邪,可以下水试一试,反正国内做 ATS 的也没几家,机会还有.
云招 OurATS 从 2010 年开始,到今年已经持续开发了 15 年,代码库现存代码上千万行,换语言重构的成本不好估量.
而 PHP 编译器 BPC 从开始开发到成功编译云招 OurATS,用了 3 年,资金投入约 500 万人民币.
说到底,PHP 真是世界上最好的语言呀!
首先,完美解决了源码保护,软件授权这两大基本需求.
如果换 java/.net 的话,这两个语言的反编译比 PHP 成熟多了.
GraalVM 和 .NET 8 的 Native AOT 是否好用还不好说.
如果换 go 的话,源码保护是没问题,但需要解决软件授权的问题,当然 java/.net 也需要解决这个问题.
BPC 编译还带来了额外好处:
软件交付变得简单了.
整个云招 OurATS 招聘系统被编译成了一个二进制可执行文件,日常升级维护就是替换这一个文件(当然整个系统的运行还需要其它几个辅助程序).
运行环境更安全了.
生产环境不需要 PHP 解释器,因为 PHP 源码已经被 BPC 最终转译成 C,然后编译成可执行文件了.
也就是说,服务器上不能执行 PHP 代码,很多针对 PHP 的攻击手段失效了.
合作方式更灵活
PHP 项目源码保护的一个做法是使用编译型语言编写部分核心逻辑,然后其它代码开源.
有了 BPC 之后,完全可以把核心 PHP 代码编译成动态链接库,其它部分开源.
BPC 的目标是源码保护和软件授权,现阶段没有在生成代码和运行性能上做特别的优化.
因此虽然是编译成 C,但性能在大多数场景下还不如解释执行的 PHP 快.
所以如果是性能敏感的项目慎用.
]]>场景这样的使用 symfony process 启动了一个 php 进程。
symfony 启动使用的命令是:
$commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code'
启动成功后进程树类似:(假设执行的是 php -r "sleep(10);")
|---master |-----|---(PID:100) sh -c php -r "sleep(10);" |-----------|---(PID:101) php -r "sleep(10);"
当在 master 中执行 posix_kill(101, 15) sigterm
后。sh 立刻退出了。剩余一个 php -r "sleep(10);" 还在跑。
通过这种形式启动的进程应该怎么“优雅”的退出呢?
]]>