首页
下载
文档
社区
视频
捐赠
开源项目
typephp
swoole
code-galaxy
swoole-cli
phpx
phpy
赞助商
AOT 编译器
AI 助理
商业产品
Swoole-Compiler 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
TypePHP
CodeGalaxy
发表新帖
TypePHP 写 PHP 扩展:把核心业务逻辑编译成扩展
写 PHP 扩展一直被认为是 C 程序员的活。但如果目的不是给 Zend 引擎加新语法,而是**把一段稳定的 PHP 业务逻辑变成扩展**,那就不用碰 C——TypePHP 能把 PHP 源码直接编译成标准 `.so` / `.dll`,用 `extension=` 加载。 这在工程上有三个真实价值:**复用**(优惠计算、风控规则这类逻辑,多个项目各写一遍容易算出不一致,收敛成一个扩展共用)、**交付**(商业 PHP 产品把核心逻辑交给客户部署,但不想给源码)、**性能**(编译成机器指令,不走 Zend VM 解释)。 下面用一个电商**优惠计算引擎**从零写一个扩展,并加载进现有 PHP 项目。所有命令和输出都在本机(PHP 8.4)实际跑过。 ## 一、对外 API 扩展名 `promo`,加载后宿主 PHP 可以调用: | 名称 | 说明 | | --- | --- | | `PROMO_VERSION` | 常量,规则版本 | | `promo_rules()` | 返回内置优惠规则 | | `promo_quote(array $cart, array $context = [])` | 计算优惠明细与应付金额 | 规则本身保持稳定(会员折扣、满减、券、运费),只有**等级、券码、运费**由宿主传入。这正是适合编译成扩展的逻辑:算法稳定,参数灵活。 ## 二、项目结构与构建配置 ```text promo-ext/ ├── project.yml # 扩展构建配置 ├── src/promo.php # 扩展源码(会被编译,不随产品分发) └── demo.php # 宿主侧调用示例(不在 sources 里,不参与编译) ``` `project.yml` 的关键是 `mode: ext`: ```yaml name: promo mode: ext version: 1.0.0 cxx-std: c++17 sources: - src info: Author: TypePHP Demo Description: E-commerce promotion and pricing engine ``` `sources` 只列 `src`,所以 `demo.php` 不会被编译进去。 ## 三、扩展源码 `src/promo.php` 就是普通 PHP。不需要写 `get_module()`、`PHP_MINFO` 这些 C 代码,模块入口由编译器生成: ```php <?php const PROMO_VERSION = '1.0.0'; function promo_rules(): array { return [ 'level_rate' => ['none' => 1.0, 'silver' => 0.95, 'gold' => 0.9], 'thresholds' => [ ['min' => 300.0, 'off' => 30.0], ['min' => 500.0, 'off' => 80.0], ], 'coupons' => [ 'SAVE20' => ['type' => 'amount', 'value' => 20.0, 'min' => 200.0], 'OFF10' => ['type' => 'percent', 'value' => 0.1, 'min' => 100.0], ], 'free_shipping_amount' => 99.0, ]; } function promo_quote(array $cart, array $context = []): array { $rules = promo_rules(); // 1. 统计金额,并判断是否全部是可下载商品 $subtotal = 0.0; $allDigital = true; foreach ($cart as $item) { $subtotal += (float)$item['price'] * (int)$item['qty']; if (($item['tag'] ?? 'physical') !== 'digital') { $allDigital = false; } } $subtotal = round($subtotal, 2); // 2. 会员折扣 $rate = (float)($rules['level_rate'][$context['level'] ?? 'none'] ?? 1.0); $memberDiscount = round($subtotal * (1 - $rate), 2); // 3. 满减:命中最高一档 $thresholdDiscount = 0.0; foreach ($rules['thresholds'] as $threshold) { if ($subtotal >= $threshold['min']) { $thresholdDiscount = (float)$threshold['off']; } } // 4. 优惠券 $coupon = $context['coupon'] ?? null; $couponDiscount = 0.0; $couponResult = null; if ($coupon !== null && $coupon !== '') { $rule = $rules['coupons'][$coupon] ?? null; if ($rule === null) { $couponResult = ['code' => $coupon, 'applied' => false, 'reason' => 'unknown_coupon']; } elseif ($subtotal < $rule['min']) { $couponResult = ['code' => $coupon, 'applied' => false, 'reason' => 'threshold_not_met']; } else { $couponDiscount = $rule['type'] === 'percent' ? round($subtotal * (float)$rule['value'], 2) : (float)$rule['value']; $couponResult = ['code' => $coupon, 'applied' => true]; } } // 5. 运费:数字商品不计运费;实付满 99 免运费 $shipping = (float)($context['shipping'] ?? 0.0); $payable = $subtotal - $memberDiscount - $thresholdDiscount - $couponDiscount; if ($allDigital || $payable >= (float)$rules['free_shipping_amount']) { $shipping = 0.0; } $total = round($payable + $shipping, 2); if ($total < 0) { $total = 0.0; } return [ 'version' => PROMO_VERSION, 'subtotal' => $subtotal, 'memberDiscount' => $memberDiscount, 'thresholdDiscount' => $thresholdDiscount, 'couponDiscount' => $couponDiscount, 'shipping' => $shipping, 'total' => $total, 'coupon' => $couponResult, ]; } ``` ## 四、编译 需要一份 PHP 8.4(**必须和宿主 PHP 完全一致**:版本、ZTS/NTS、debug 标志)和 PHPX 源码: ```bash $ cd /path/to/typephp/compiler $ export PHP_HOME=/path/to/php-8.4 PHPX_HOME=/path/to/typephp/phpx $ ./tpc examples/promo-ext/project.yml --no-progress -j4 Initialized platform/backend: Linux + GCC (g++) Successfully compiled 2 files g++ '.../promo.so.rsp' -o 'promo.so' -L'.../phpx/lib' -shared \ -Wl,-rpath,'.../phpx/lib' -lphpx -lgmp -lgmpxx -lmpfr -lstdc++ Build successful: promo.so ``` 产物就是当前目录下的 `promo.so`,**和普通 C 扩展没有任何区别**。 ## 五、宿主项目加载调用 `demo.php` 完全是"使用方"的写法,不 include 任何源码: ```php $bigCart = [ ['price' => 299.0, 'qty' => 1, 'tag' => 'physical'], ['price' => 120.0, 'qty' => 2, 'tag' => 'physical'], ]; $r = promo_quote($bigCart, ['level' => 'gold', 'coupon' => 'SAVE20', 'shipping' => 12.0]); printf("应付:%.2f\n", $r['total']); ``` ```bash $ php -d extension=$PWD/promo.so examples/promo-ext/demo.php 扩展 typephp_promo:已加载,版本 1.0.0 A. 黄金会员 + SAVE20 小计 539.00 会员 -53.90 满减 -80.00 券 -20.00 运费 +0.00 => 应付 385.10 B. 白银会员 + OFF10 小计 539.00 会员 -26.95 满减 -80.00 券 -53.90 运费 +0.00 => 应付 378.15 C. 普通会员 / 小车 / 收运费 小计 45.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +12.00 => 应付 57.00 D. 数字商品 / 不收运费 小计 45.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +0.00 => 应付 45.00 E. SAVE20 未满 200 门槛 小计 120.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +0.00 => 应付 120.00 券 SAVE20 未生效:threshold_not_met F. 不存在的券码 小计 539.00 会员 -53.90 满减 -80.00 券 -0.00 运费 +0.00 => 应付 405.10 券 FAKE 未生效:unknown_coupon ``` 会员折扣、满减、两类优惠券、免运费、数字商品、券门槛不足、非法券,全部走通。 ## 六、扩展信息与部署 加载后它就是一个标准 PHP 模块。注意**模块名带 `typephp_` 前缀**,文件名才是 `promo.so`: ```bash $ php -d extension=$PWD/promo.so -m | grep typephp_promo typephp_promo $ php -d extension=$PWD/promo.so --ri typephp_promo typephp_promo support => enabled Author => TypePHP Demo Description => E-commerce promotion and pricing engine ``` 这些信息来自 `project.yml` 的 `version` / `info`,不需要手写 `PHP_MINFO`。部署时有三个点: 1. **扩展只依赖 PHPX**。`ldd promo.so` 里的 PHP 相关依赖只有 `libphpx.so`,Zend/PHP 符号由宿主 SAPI 解析。编译时已写入 rpath,把 PHPX 库放到目标机同一路径即可。 2. **ABI 必须匹配**宿主 PHP(版本、ZTS/NTS、debug 标志),否则加载会崩。 3. **源码保护要说清楚**:`strings promo.so | grep -c 'function promo_quote'` 结果是 `0`,编译产物里没有 PHP 源码;但字符串常量(比如券码)仍在二进制里。所以这是"提高逆向成本",不是"不可破解"。真要做授权校验,请用非对称签名或服务端校验,别把密钥硬编码进逻辑。 ## 七、结语 一份普通 PHP 业务代码,加一个十来行的 `project.yml`,就能编译成标准扩展,被 Laravel、ThinkPHP 或自研框架直接 `extension=` 加载。适用边界也很清楚:**算法稳定、参数灵活、可能被多个项目复用或有交付需求**的逻辑,最适合下沉成扩展;规则天天变的配置项,还是留在数据库或配置中心。 完整示例见仓库 `compiler/examples/promo-ext/`。 ## 技术社区 TypePHP 由识沃科技(Swoole 团队)主导研发。欢迎添加识沃客服微信,加入技术交流群,与开发者直接交流、获取最新版本与构建指南。 
发布于2周前 · 2 次浏览 · 来自
TypePHP
Rango-H
写 PHP 扩展一直被认为是 C 程序员的活。但如果目的不是给 Zend 引擎加新语法,而是**把一段稳定的 PHP 业务逻辑变成扩展**,那就不用碰 C——TypePHP 能把 PHP 源码直接编译成标准 `.so` / `.dll`,用 `extension=` 加载。 这在工程上有三个真实价值:**复用**(优惠计算、风控规则这类逻辑,多个项目各写一遍容易算出不一致,收敛成一个扩展共用)、**交付**(商业 PHP 产品把核心逻辑交给客户部署,但不想给源码)、**性能**(编译成机器指令,不走 Zend VM 解释)。 下面用一个电商**优惠计算引擎**从零写一个扩展,并加载进现有 PHP 项目。所有命令和输出都在本机(PHP 8.4)实际跑过。 ## 一、对外 API 扩展名 `promo`,加载后宿主 PHP 可以调用: | 名称 | 说明 | | --- | --- | | `PROMO_VERSION` | 常量,规则版本 | | `promo_rules()` | 返回内置优惠规则 | | `promo_quote(array $cart, array $context = [])` | 计算优惠明细与应付金额 | 规则本身保持稳定(会员折扣、满减、券、运费),只有**等级、券码、运费**由宿主传入。这正是适合编译成扩展的逻辑:算法稳定,参数灵活。 ## 二、项目结构与构建配置 ```text promo-ext/ ├── project.yml # 扩展构建配置 ├── src/promo.php # 扩展源码(会被编译,不随产品分发) └── demo.php # 宿主侧调用示例(不在 sources 里,不参与编译) ``` `project.yml` 的关键是 `mode: ext`: ```yaml name: promo mode: ext version: 1.0.0 cxx-std: c++17 sources: - src info: Author: TypePHP Demo Description: E-commerce promotion and pricing engine ``` `sources` 只列 `src`,所以 `demo.php` 不会被编译进去。 ## 三、扩展源码 `src/promo.php` 就是普通 PHP。不需要写 `get_module()`、`PHP_MINFO` 这些 C 代码,模块入口由编译器生成: ```php <?php const PROMO_VERSION = '1.0.0'; function promo_rules(): array { return [ 'level_rate' => ['none' => 1.0, 'silver' => 0.95, 'gold' => 0.9], 'thresholds' => [ ['min' => 300.0, 'off' => 30.0], ['min' => 500.0, 'off' => 80.0], ], 'coupons' => [ 'SAVE20' => ['type' => 'amount', 'value' => 20.0, 'min' => 200.0], 'OFF10' => ['type' => 'percent', 'value' => 0.1, 'min' => 100.0], ], 'free_shipping_amount' => 99.0, ]; } function promo_quote(array $cart, array $context = []): array { $rules = promo_rules(); // 1. 统计金额,并判断是否全部是可下载商品 $subtotal = 0.0; $allDigital = true; foreach ($cart as $item) { $subtotal += (float)$item['price'] * (int)$item['qty']; if (($item['tag'] ?? 'physical') !== 'digital') { $allDigital = false; } } $subtotal = round($subtotal, 2); // 2. 会员折扣 $rate = (float)($rules['level_rate'][$context['level'] ?? 'none'] ?? 1.0); $memberDiscount = round($subtotal * (1 - $rate), 2); // 3. 满减:命中最高一档 $thresholdDiscount = 0.0; foreach ($rules['thresholds'] as $threshold) { if ($subtotal >= $threshold['min']) { $thresholdDiscount = (float)$threshold['off']; } } // 4. 优惠券 $coupon = $context['coupon'] ?? null; $couponDiscount = 0.0; $couponResult = null; if ($coupon !== null && $coupon !== '') { $rule = $rules['coupons'][$coupon] ?? null; if ($rule === null) { $couponResult = ['code' => $coupon, 'applied' => false, 'reason' => 'unknown_coupon']; } elseif ($subtotal < $rule['min']) { $couponResult = ['code' => $coupon, 'applied' => false, 'reason' => 'threshold_not_met']; } else { $couponDiscount = $rule['type'] === 'percent' ? round($subtotal * (float)$rule['value'], 2) : (float)$rule['value']; $couponResult = ['code' => $coupon, 'applied' => true]; } } // 5. 运费:数字商品不计运费;实付满 99 免运费 $shipping = (float)($context['shipping'] ?? 0.0); $payable = $subtotal - $memberDiscount - $thresholdDiscount - $couponDiscount; if ($allDigital || $payable >= (float)$rules['free_shipping_amount']) { $shipping = 0.0; } $total = round($payable + $shipping, 2); if ($total < 0) { $total = 0.0; } return [ 'version' => PROMO_VERSION, 'subtotal' => $subtotal, 'memberDiscount' => $memberDiscount, 'thresholdDiscount' => $thresholdDiscount, 'couponDiscount' => $couponDiscount, 'shipping' => $shipping, 'total' => $total, 'coupon' => $couponResult, ]; } ``` ## 四、编译 需要一份 PHP 8.4(**必须和宿主 PHP 完全一致**:版本、ZTS/NTS、debug 标志)和 PHPX 源码: ```bash $ cd /path/to/typephp/compiler $ export PHP_HOME=/path/to/php-8.4 PHPX_HOME=/path/to/typephp/phpx $ ./tpc examples/promo-ext/project.yml --no-progress -j4 Initialized platform/backend: Linux + GCC (g++) Successfully compiled 2 files g++ '.../promo.so.rsp' -o 'promo.so' -L'.../phpx/lib' -shared \ -Wl,-rpath,'.../phpx/lib' -lphpx -lgmp -lgmpxx -lmpfr -lstdc++ Build successful: promo.so ``` 产物就是当前目录下的 `promo.so`,**和普通 C 扩展没有任何区别**。 ## 五、宿主项目加载调用 `demo.php` 完全是"使用方"的写法,不 include 任何源码: ```php $bigCart = [ ['price' => 299.0, 'qty' => 1, 'tag' => 'physical'], ['price' => 120.0, 'qty' => 2, 'tag' => 'physical'], ]; $r = promo_quote($bigCart, ['level' => 'gold', 'coupon' => 'SAVE20', 'shipping' => 12.0]); printf("应付:%.2f\n", $r['total']); ``` ```bash $ php -d extension=$PWD/promo.so examples/promo-ext/demo.php 扩展 typephp_promo:已加载,版本 1.0.0 A. 黄金会员 + SAVE20 小计 539.00 会员 -53.90 满减 -80.00 券 -20.00 运费 +0.00 => 应付 385.10 B. 白银会员 + OFF10 小计 539.00 会员 -26.95 满减 -80.00 券 -53.90 运费 +0.00 => 应付 378.15 C. 普通会员 / 小车 / 收运费 小计 45.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +12.00 => 应付 57.00 D. 数字商品 / 不收运费 小计 45.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +0.00 => 应付 45.00 E. SAVE20 未满 200 门槛 小计 120.00 会员 -0.00 满减 -0.00 券 -0.00 运费 +0.00 => 应付 120.00 券 SAVE20 未生效:threshold_not_met F. 不存在的券码 小计 539.00 会员 -53.90 满减 -80.00 券 -0.00 运费 +0.00 => 应付 405.10 券 FAKE 未生效:unknown_coupon ``` 会员折扣、满减、两类优惠券、免运费、数字商品、券门槛不足、非法券,全部走通。 ## 六、扩展信息与部署 加载后它就是一个标准 PHP 模块。注意**模块名带 `typephp_` 前缀**,文件名才是 `promo.so`: ```bash $ php -d extension=$PWD/promo.so -m | grep typephp_promo typephp_promo $ php -d extension=$PWD/promo.so --ri typephp_promo typephp_promo support => enabled Author => TypePHP Demo Description => E-commerce promotion and pricing engine ``` 这些信息来自 `project.yml` 的 `version` / `info`,不需要手写 `PHP_MINFO`。部署时有三个点: 1. **扩展只依赖 PHPX**。`ldd promo.so` 里的 PHP 相关依赖只有 `libphpx.so`,Zend/PHP 符号由宿主 SAPI 解析。编译时已写入 rpath,把 PHPX 库放到目标机同一路径即可。 2. **ABI 必须匹配**宿主 PHP(版本、ZTS/NTS、debug 标志),否则加载会崩。 3. **源码保护要说清楚**:`strings promo.so | grep -c 'function promo_quote'` 结果是 `0`,编译产物里没有 PHP 源码;但字符串常量(比如券码)仍在二进制里。所以这是"提高逆向成本",不是"不可破解"。真要做授权校验,请用非对称签名或服务端校验,别把密钥硬编码进逻辑。 ## 七、结语 一份普通 PHP 业务代码,加一个十来行的 `project.yml`,就能编译成标准扩展,被 Laravel、ThinkPHP 或自研框架直接 `extension=` 加载。适用边界也很清楚:**算法稳定、参数灵活、可能被多个项目复用或有交付需求**的逻辑,最适合下沉成扩展;规则天天变的配置项,还是留在数据库或配置中心。 完整示例见仓库 `compiler/examples/promo-ext/`。 ## 技术社区 TypePHP 由识沃科技(Swoole 团队)主导研发。欢迎添加识沃客服微信,加入技术交流群,与开发者直接交流、获取最新版本与构建指南。 
赞
0
收藏
提问
分享
讨论
建议
公告
开发框架
TypePHP
CodeGalaxy
登录
后参与评论
评论
还没有评论!