首页
下载
文档
社区
视频
捐赠
开源项目
typephp
swoole
code-galaxy
swoole-cli
phpx
phpy
赞助商
AOT 编译器
AI 助理
商业产品
Swoole-Compiler 代码加密器
CRMEB 新零售社交电商系统
登录
注册
全部
提问
分享
讨论
建议
公告
开发框架
TypePHP
CodeGalaxy
发表新帖
TypePHP 四种运行时方案全解:Nano、全静态、PHP Builder 与宿主机 libphp
使用 TypePHP 编译一个项目时,经常会看到这些参数: ```text --nano --full-static --php-builder -m bin / -m lib / -m ext --sapi=embed / cli / fpm ``` 它们看起来都在决定“怎么编译”,但实际上描述的是三个不同问题: 1. **产物是什么**:可执行文件、动态库,还是 PHP 扩展; 2. **谁负责启动 PHP**:Embed、CLI、FPM,还是外部原生宿主; 3. **PHP 运行时从哪里来**:宿主机、源码现编、全静态 SDK,还是 PHP Nano 源码。 只要先把这三条轴分开,TypePHP 的构建方式就不再复杂。 本文将详细介绍四种运行时与链接方案: - 默认链接宿主机 `libphp.so`; - 使用 `--php-builder` 从 php-src 构建私有运行时; - 使用 `--full-static` 生成无动态库依赖的 Linux 程序; - 使用 `--nano` 生成不包含 ZendVM 的轻量原生产物。 还会说明 Android、iOS、macOS、Windows 原生应用分别支持到什么程度,以及 `bin`、`lib`、`ext` 到底有什么区别。 --- ## 一、先建立正确的心智模型 TypePHP 的核心配置可以拆成下面三层: | 层次 | 配置 | 回答的问题 | | --- | --- | --- | | 产物类型 | `mode: bin/lib/ext` | 最终生成可执行文件、共享库还是 PHP 扩展? | | 进程接口 | `sapi: embed/cli/fpm` | `bin` 可执行文件以哪一种 PHP SAPI 启动? | | 运行时来源 | 默认 / `--php-builder` / `--full-static` / `--nano` | PHP、PHPX 和底层库从哪里来? | 其中有两条必须牢记: - `cli`、`fpm` 是 **SAPI**,不是 mode;它们生成的仍然是 ELF、Mach-O 或 PE 可执行文件; - `--nano`、`--full-static` 和 `--php-builder` 不是三种产物,它们是在改变运行时 的组成与链接方式。 例如: ```yaml mode: bin sapi: cli entry: bin/console.php php-builder: {} ``` 这段配置的完整含义是: > 生成一个可执行文件;使用 PHP CLI SAPI 启动;PHP 运行时不取宿主机现成库, > 而是从 php-src 重新构建并静态链接。 --- ## 二、默认方案:链接宿主机 libphp 不添加 `--nano`、`--full-static` 或 `--php-builder` 时,TypePHP 使用完整 PHP 运行时。Linux 上通常是 `libphp.so`,macOS 上是 `libphp.dylib`,Windows 则通过 import library 链接工具包中的 PHP DLL。 最简单的程序: ```php <?php function main(int $argc, array $argv): void { echo "Hello TypePHP\n"; var_dump(PHP_VERSION, $argv); } ``` 直接编译: ```bash vendor/bin/tpc.php hello.php ./hello first second ``` 默认等价于: ```yaml mode: bin sapi: embed ``` ### 链接过程 以 Linux 为例,链接关系可以简化为: ```text TypePHP 生成的 .o + libphpx.so + libphp.so + GMP / MPFR / libstdc++ 等系统库 ↓ hello(ELF) ``` TypePHP 通常优先使用 PHPX 的共享库和 PHP Embed 共享库,并在 ELF/Mach-O 中写入 对应的运行时搜索路径。最终机器仍然要能找到这些共享库。 可以检查实际依赖: ```bash ldd ./hello # Linux otool -L ./hello # macOS dumpbin /dependents hello.exe # Windows ``` ### 这种方案的优点 - 编译最快,不需要重新构建 PHP; - 产物较小; - 使用完整 PHP、ZendVM 和宿主机已经准备好的扩展能力; - 很适合开发机、固定服务器和统一镜像环境。 ### 需要注意的问题 - 目标机器必须提供 ABI 匹配的 PHP/PHPX 库; - PHP 版本、ZTS/NTS、Debug/Release 和编译选项必须一致; - “机器上能执行 `php`”不代表存在 Embed SAPI 使用的 `libphp.so`;很多发行版只 安装 PHP CLI/FPM; - 复制单个可执行文件到另一台机器,可能因为缺少库或版本不一致而无法启动。 Linux/macOS 缺少 Embed 库时,在交互式终端中 TypePHP 会询问是否切换到 `php-builder`。在 CI 等非交互环境中,应显式添加 `--php-builder`,否则构建会停止。 ### `ext` 模式为什么不链接 libphp.so 这是一个很重要的例外。`mode: ext` 生成的是由现有 PHP 进程加载的扩展: ```text php-fpm / php-cli / Apache SAPI ↓ 已经拥有 ZendVM、全局表和内存管理器 typephp_demo.so ``` 在 Linux/macOS 上,扩展不应再链接一个 `libphp.so`,否则同一进程可能出现两套 ZendVM 全局状态。扩展中的 Zend/PHP 符号由加载它的 PHP SAPI 解析。 它仍然需要匹配的 PHP 头文件、ABI,以及进程级唯一的 PHPX 共享运行时。 --- ## 三、`--php-builder`:从 php-src 构建私有完整 PHP `--php-builder` 的目标是: > 不依赖宿主机现成的 `libphp.so`,从官方 php-src 构建一套项目可用的私有静态 > PHP 运行时。 最简单的用法: ```bash vendor/bin/tpc.php hello.php --php-builder ``` 不带值的 `--php-builder` 等价于空映射 `{}`。如果要指定扩展和 ZTS: ```bash vendor/bin/tpc.php project.yml \ --php-builder='extensions: [swoole, mongodb]; zts: on' ``` YAML 写法是: ```yaml name: myapp mode: bin sapi: embed php-builder: extensions: [swoole, mongodb] zts: on sources: - src ``` 请注意,`sapi` 不属于 `php-builder`。下面这种写法是错误的: ```yaml php-builder: sapi: cli # 错误 ``` 应该写成: ```yaml sapi: cli entry: bin/console.php php-builder: {} ``` ### 它构建了什么 第一次构建时,TypePHP 会: 1. 下载与 `php-version` 匹配的官方 PHP 源码包; 2. 从源码、YAML、Composer `ext-*` 依赖中收集所需扩展; 3. 为 php-src 自动生成 configure 参数; 4. 将 php-src 内置扩展和外部 PECL 扩展静态编入 PHP; 5. 构建目标 SAPI、`libphp.a` 和匹配的 `libphpx.a`; 6. 将生成的 TypePHP 模块链接进最终可执行文件。 官方 php-src 缓存不会被直接修改。需要注入 PECL 扩展时,会创建派生源码目录。 缓存位于: ```text ~/.typephp/ ├── archives/ # PHP、PECL 源码包 ├── src/ # 未修改的官方 php-src ├── pecl/ # PECL 解压目录 ├── php-builder-src/ # 注入外部扩展后的派生源码 └── php-builder/ # 构建好的私有运行时 ``` 类似下面的目录名: ```text php-8.5.10-e90c19d6ad02c94f ``` 末尾哈希是兼容性指纹,包含 PHP 补丁版本、SAPI、ZTS、扩展集合、系统、CPU 和 工具链,不包含项目路径。因此多个兼容项目可以复用同一套运行时,并不是每个项目 复制一份 PHP 源码。 ### Embed、CLI、FPM 三种 SAPI `php-builder` 同时支持 PHP 的三种启动方式: | SAPI | 用途 | 是否需要 `entry` | | --- | --- | --- | | `embed` | TypePHP 自己拥有 `main`,进程内嵌完整 PHP | 否 | | `cli` | 构建官方 PHP CLI 主程序,并静态注册 TypePHP 模块 | 是 | | `fpm` | 构建包含 TypePHP 模块的 PHP-FPM | 否 | CLI 示例: ```yaml name: worker mode: bin sapi: cli entry: bin/worker.php php-builder: extensions: [pcntl, sockets] zts: off sources: - src ``` `entry` 是 ZendVM 在 CLI 启动时执行的 PHP 主脚本;`sources` 中的业务代码仍然由 TypePHP AOT 编译。CLI 入口不需要定义 TypePHP Embed 模式的全局 `main()`。 FPM 示例: ```yaml name: myapp-fpm mode: bin sapi: fpm php-builder: extensions: [pdo, pdo_mysql, opcache] zts: off sources: - src ``` 也可以一次构建多个 SAPI。YAML 使用列表,命令行使用逗号分隔: ```yaml mode: bin sapi: [embed, cli, fpm] entry: bin/console.php php-builder: extensions: [] zts: on ``` ```bash vendor/bin/tpc.php project.yml \ --sapi=embed,cli,fpm \ --entry=bin/console.php \ --php-builder='extensions: []; zts: on' ``` 多目标构建会分别产生带 `-embed`、`-cli`、`-fpm` 后缀的程序。只要 SAPI 列表包含 `cli`,就必须提供 `entry`;若配置了 `entry` 却没有选择 `cli`,TypePHP 只会给出 warning 并忽略它。 Embed 与 CLI 程序通常在一次进程执行中只经历一次请求初始化和关闭。FPM 不同:它会 在常驻 Worker 中为每个 HTTP 请求反复执行 RINIT/RSHUTDOWN。即使模块已经静态注册, 也不能在这两个阶段加入大规模循环、全表遍历或其他昂贵工作。 下载 PHP 或 PECL 源码需要经过代理时,使用独立的全局参数 `--proxy`: ```bash vendor/bin/tpc.php project.yml --php-builder \ --proxy=http://127.0.0.1:7890 ``` `--proxy` 不属于 `php-builder`;其他由 TypePHP 发起的下载、上传等网络行为也应复用 这一代理设置。 ### 为什么它不是全静态 `php-builder` 会把 PHP、PHPX 和选中的 PHP 扩展静态编进可执行文件,但底层库仍由 操作系统提供,例如: - libc; - libxml2; - zlib; - OpenSSL; - SQLite; - 扩展依赖的其他系统库。 所以它解决的是“不要依赖宿主机 PHP”,不是“不要依赖任何动态库”。 ```bash ldd ./myapp ``` 仍然可能看到多项系统共享库,这是正常的。 ### 当前平台边界 - 支持 Linux、macOS; - 只适用于 `mode: bin`; - `cli` 和 `fpm` 强制依赖 `php-builder`; - Windows、Android、iOS 当前不使用 `php-builder`; - 编译器本身仍由宿主 PHP 执行,`php-builder` 替换的是目标程序运行时。 --- ## 四、`--full-static`:真正没有动态库依赖 `--full-static` 面向 Linux 可执行文件分发。它使用专用 SDK,把以下内容一起静态 链接进最终 ELF: - 完整 PHP 和 ZendVM; - PHPX; - PHP 扩展; - 第三方依赖; - C/C++ 运行时; - musl libc 和启动文件。 因此它和 `php-builder` 的根本区别是: | | `--php-builder` | `--full-static` | | --- | --- | --- | | PHP 来源 | 构建时从 php-src 编译 | 使用预制全静态 SDK | | PHP/ZendVM | 完整版 | 完整版 | | PHP 扩展 | 按项目需求构建 | 由 SDK 预先提供 | | 系统动态库 | 仍然依赖 | 目标是 0 个 | | libc | 操作系统提供 | SDK 内的 musl | | 适合场景 | 固定 Linux/macOS 系统,自带私有 PHP | 跨 Linux 发行版复制单文件运行 | 这里最容易产生误解: > **全静态不等于 Nano。** 全静态程序仍然包含完整 PHP 和 ZendVM,动态 PHP 能力与扩展能力取决于 SDK;它只 是在链接层面把所有库装进了一个 ELF。 ### 准备 SDK 从 PHPX Releases 下载与 CPU、PHP ABI 匹配的 full-static SDK,并确保目录结构为: ```text $PHPX_HOME/full-static/sdk/ ├── include/ └── lib/ ├── libphp.a ├── libphpx.a └── musl/ ├── crt1.o ├── crti.o └── crtn.o ``` 然后编译: ```bash export PHPX_HOME=/path/to/phpx vendor/bin/tpc.php hello.php --full-static --compiler=/usr/bin/clang ``` 全静态 SDK 中的 `libphp.a` 使用 musl,链接阶段也必须使用对应的 musl target 和 启动文件。TypePHP 会自动设置目标三元组、`-static` 和 SDK 内的启动文件路径。 当前 `--full-static` 需要能够生成 Linux musl 目标的原生 Clang。若显式传入的 `--compiler` 不支持对应 target,编译器会直接报错,而不会偷偷换成另一套工具链。 ### 验证是否真的全静态 ```bash file ./hello ldd ./hello readelf -dW ./hello | grep -E 'NEEDED|INTERP' ``` 预期结果是: - `file` 显示 `statically linked`; - `ldd` 显示“不是动态可执行文件”或等价信息; - `readelf` 没有 `NEEDED` 和动态解释器记录。 全静态产物通常明显大于普通程序,因为完整 PHP、扩展、libc 和第三方库都在同一个 文件里。它适合交付、离线环境、基础系统差异较大的服务器,但仍必须匹配 CPU 架构, 也仍然受 Linux 内核与系统调用兼容性约束。 ### 当前限制 - 只支持 `mode: bin`; - 不支持 `lib` 和 `ext`,共享库不能安全地再携带一套 libc; - 面向 Linux musl ELF,不用于 macOS、Windows、Android 或 iOS; - 依赖专用 full-static SDK,不能用普通 `libphp.a` 代替; - 不能和 `--nano` 组合; - 不应与 `--php-builder` 组合,两者是不同的运行时来源方案。 --- ## 五、`--nano`:不包含 ZendVM 的源码组合运行时 Nano 解决的是另一个问题: > 最终产物不链接完整 `libphp`,而是只把 PHP Nano、PHPX 和实际需要的运行时源码 > 编进应用。 安装 PHP Nano 1.0.2 或更高版本: ```bash composer require --dev swoole/typephp "swoole/php-nano:^1.0.2" ``` 编译: ```bash vendor/bin/tpc.php hello.php --nano ./hello ``` 在 Linux、macOS、Android、iOS 上,构建关系大致是: ```text TypePHP 生成的 C++ + PHP Nano 源码 + PHPX 源码(PHPX_NANO) + 被静态选择的 Composer 原生扩展 ↓ 原生 bin / lib ``` 最终产物不需要 `libphp.so`、`libphp.dylib`、`libphp.a` 或预编译 `libphpx`。 ### Nano 不是什么 Nano 不是把 `tpc.php` 自己编译成一个微型编译器。TypePHP 编译器需要动态执行 `nikic/php-parser` 等 Composer 包,所以 `tpc.php` 仍然运行在构建机的完整 PHP 上。 `--nano` 只作用于生成的目标程序。 Nano 也不等于操作系统层面的“全静态”。普通 Linux Nano 程序仍可能依赖 libc、 libstdc++ 或目标平台框架。它不依赖的是完整 PHP/PHPX 运行库。 因此: - `--full-static`:完整 PHP + ZendVM,链接层零动态库; - `--nano`:没有完整 PHP/ZendVM,但可能仍有操作系统动态依赖。 ### 能力边界 Nano 复用 PHP 的 `zval`、字符串、数组、对象、异常、GC 和一部分内置扩展源码,但 移除了 ZendVM 解释执行和不符合轻量原生模型的宿主能力。 不支持的典型能力包括: - `eval`、`include`、`require`; - Generator、Fiber、匿名类; - 动态加载 PHP 扩展; - 外部命令和进程 API; - socket、DNS、远程 stream; - 依赖动态 PHP 源码执行的框架机制。 Nano 更适合代码边界明确的服务、工具、嵌入式组件和原生 GUI 应用,而不是直接把 任意传统 PHP 框架原封不动搬进去。 ### `bin` 与 `lib` Nano 支持: - `mode: bin`:产物自己提供 `main`,调用 TypePHP 全局 `main()`; - `mode: lib`:不生成 `main`,由 Android JNI、Apple 桥或其他原生宿主初始化; - 不支持 `mode: ext`。 共享库配置示例: ```yaml name: my_runtime mode: lib output: build/libmy_runtime.so cxx-std: c++17 sources: - src - bridge ``` ```bash vendor/bin/tpc.php project.yml --nano ``` 宿主通过 `typephp_runtime.h` 中的项目级 C ABI 初始化运行时: ```cpp #include <typephp_runtime.h> TYPEPHP_RUNTIME_INIT_FUNCTION(my_runtime); TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(my_runtime); if (TYPEPHP_RUNTIME_INIT(my_runtime)(argc, argv) != 0) { return 1; } // 此后可以调用 TypePHP 导出的函数 TYPEPHP_RUNTIME_SHUTDOWN(my_runtime)(); ``` 初始化应覆盖整个进程或原生库生命周期,不能在每次按钮点击、JNI 调用或业务函数 执行前重复初始化。Nano 的 MINIT/RINIT 在初始化时执行一次,RSHUTDOWN/MSHUTDOWN 在关闭时执行一次。 ### Windows 上的特殊情况 当前 php-nano 源码组合后端不支持 Windows。Windows 上使用 `--nano` 时: - 会应用 Nano 的语法和能力限制; - 仍然链接完整的 PHP/PHPX DLL; - 产物并不是“不依赖 PHP DLL”的真正 Nano 程序。 所以如果目标是缩小 Windows 运行时或彻底移除 ZendVM,当前版本还做不到;Windows 上的 `--nano` 更准确地说是 Nano policy 模式。 --- ## 六、`bin`、`lib`、`ext` 到底有什么区别 ### 1. `mode: bin`:拥有进程入口 `bin` 生成平台可执行文件: | 平台 | 产物 | | --- | --- | | Linux | ELF 可执行文件 | | macOS/iOS | Mach-O 可执行文件 | | Windows | PE `.exe` | | Android | Native executable,普通 App 通常不直接使用 | 默认 Embed 和 Nano `bin` 都要求 TypePHP 源码中存在全局 `main()`。CLI SAPI 是例外, 它通过 `entry` 指定由 ZendVM 执行的入口脚本。 只有 `bin` 可以选择 `sapi`,也只有 `bin` 支持 `php-builder` 和 `--full-static`。 ### 2. `mode: lib`:由外部原生宿主加载 `lib` 生成 `.so`、`.dylib` 或 `.dll`,不提供操作系统 `main`。典型用途是: - Android App 的 JNI 共享库; - 被 Objective-C++、Swift、C/C++ 程序调用; - 被另一个 TypePHP 项目链接; - 把一组 TypePHP 函数作为原生 ABI 发布。 普通模式的 `lib` 仍依赖完整 PHP/PHPX 运行时;Nano `lib` 则直接包含 Nano/PHPX 源码。TypePHP 会生成公开函数声明与导入 stub,并导出项目级运行时初始化/关闭函数。 ### 3. `mode: ext`:由现有 PHP SAPI 加载 `ext` 生成 PHP 扩展:Linux/macOS 通常是 `.so`/bundle,Windows 是 `.dll`。它没有 自己的 `main`,也不应该启动第二套 PHP 运行时。 扩展由 PHP CLI、PHP-FPM、Apache 等现有进程加载,因此: - MINIT/MSHUTDOWN 对应 PHP 模块生命周期; - RINIT/RSHUTDOWN 会随 PHP 请求反复执行; - RINIT/RSHUTDOWN 中不能放大量循环、全表遍历或昂贵初始化; - 进程级只读元数据和字面量应尽可能在 MINIT 阶段一次性建立。 Nano 不支持 `mode: ext`,因为 Nano 本身就是运行时,不存在“加载进另一套 Nano PHP” 的动态扩展场景。 ### 三种“扩展”不要混淆 TypePHP 中“扩展”一词可能出现在三个位置: | 名称 | 实际含义 | | --- | --- | | `mode: ext` | 生成一个可被外部 PHP 加载的 TypePHP 扩展 | | `php-builder.extensions` | 把 PHP/PECL 扩展静态编进私有完整 PHP 运行时 | | Nano Composer 原生扩展 | 把符合 Nano 能力边界的扩展源码静态编进 Nano `bin/lib` | 后两者都不是 `mode: ext`。 --- ## 七、Android、iOS、macOS、Windows 支持矩阵 ### 运行时方案 | 运行时方案 | Linux | macOS | Windows | Android | iOS | | --- | --- | --- | --- | --- | --- | | 默认完整运行时 | ✅ 宿主 `libphp.so` | ✅ 宿主 `libphp.dylib` | ✅ 工具包 PHP/PHPX DLL | ✅ 目标平台 SDK | ✅ 目标平台 SDK | | `--php-builder` | ✅ | ✅ | ❌ | ❌ | ❌ | | `--full-static` | ✅ Linux musl ELF | ❌ | ❌ | ❌ | ❌ | | `--nano` 源码组合 | ✅ | ✅ | ⚠️ 仅 policy,仍用 DLL | ✅ | ✅ | ### 原生应用形态 | 平台 | 推荐组合 | 说明 | | --- | --- | --- | | Android | `--nano + mode: lib` | 生成 JNI `.so`,Java Activity 只负责生命周期和 View 桥接;不需要 Android `libphp.a/libphpx.a` | | iPhone 真机 | `--nano + mode: bin` | Xcode iPhoneOS 工具链生成 Mach-O;仍需证书与 Provisioning Profile | | iOS Simulator | `--nano + mode: bin` | Apple silicon 可直接编译 simulator 目标;不需要独立 PHP/PHPX 静态 SDK | | macOS AppKit | 默认 Embed 或 `--nano + mode: bin` | 默认方式依赖宿主库;Nano 直接编译运行时源码 | | Windows GUI | 默认完整运行时 `mode: bin/lib` | 使用工具包 DLL,可配合原生 Win32/C++ 桥和 `--no-console`;目前没有真正 DLL-free Nano | Android/iOS 的普通非 Nano 构建也可以使用,但需要分别准备与目标架构、PHP ABI 和 工具链匹配的 `libphp.a`、`libphpx.a` 及头文件 SDK。宿主机 Linux/macOS 的静态库 不能直接拿来交叉链接移动端。 移动应用通常选择 `bin` 或 `lib`。`ext` 需要一个先存在的 PHP SAPI 负责加载,在 Android/iOS App 中一般没有这种宿主,因此不是移动原生应用的部署方式。 --- ## 八、四种方案放在一起比较 | 对比项 | 宿主机 libphp | `--php-builder` | `--full-static` | `--nano` | | --- | --- | --- | --- | --- | | 完整 ZendVM | ✅ | ✅ | ✅ | ❌ | | 依赖宿主 PHP 运行库 | ✅ | ❌ | ❌ | 非 Windows:❌ | | 依赖系统动态库 | ✅ | ✅ | ❌ | 通常 ✅ | | PHP 扩展来源 | 宿主 PHP | 自动收集后源码构建 | SDK 预置 | Composer 源码静态选择 | | 首次构建速度 | 最快 | 慢,需要编译 PHP | 快,直接使用 SDK | 慢,需要编译 Nano/PHPX 源码 | | 增量构建 | 快 | 复用私有运行时缓存 | 复用 SDK 与对象缓存 | 复用 Nano 对象缓存 | | 产物体积 | 小 | 中到大 | 最大 | 按使用能力裁剪 | | `bin` | ✅ | ✅ | ✅ | ✅ | | `lib` | ✅ | ❌ | ❌ | ✅ | | `ext` | ✅ | ❌ | ❌ | ❌ | | 主要平台 | Linux/macOS/Windows | Linux/macOS | Linux | Linux/macOS/Android/iOS;Windows 仅 policy | 表中的“宿主机 libphp”在移动端应理解为“目标平台完整 PHP SDK”,不能使用构建机 自己的库。 --- ## 九、应该如何选择 ### 场景 1:开发机或固定服务器 选择默认宿主机 `libphp.so`: ```bash vendor/bin/tpc.php app.php ``` 优点是最快、最简单。只要部署机器和构建机器的 PHP/PHPX ABI 一致即可。 ### 场景 2:目标机没有 Embed PHP,但允许依赖系统库 选择 `--php-builder`: ```bash vendor/bin/tpc.php app.php \ --php-builder='extensions: [curl, opcache]; zts: off' ``` 它特别适合想要私有 PHP 版本、固定扩展集合,又不想自己维护 SDK 的 Linux/macOS 项目。 ### 场景 3:一个文件复制到不同 Linux 发行版运行 选择 `--full-static`: ```bash vendor/bin/tpc.php app.php --full-static --compiler=/usr/bin/clang ``` 前提是准备好匹配架构和 PHP ABI 的 full-static SDK。 ### 场景 4:移动端、桌面 GUI、嵌入式原生宿主 优先考虑 `--nano`: ```bash vendor/bin/tpc.php project.yml --nano ``` Android 通常使用 `mode: lib`,Apple 桌面/移动示例可以使用 `mode: bin`。如果业务 依赖动态 Composer PHP 代码、网络、进程或 ZendVM 特性,则改用目标平台完整 SDK。 ### 场景 5:把 TypePHP 代码交给现有 PHP-FPM 加载 选择 `mode: ext`: ```bash vendor/bin/tpc.php extension.yml -m ext -o my_extension ``` 此时要特别审查 RINIT/RSHUTDOWN 性能,因为它们会在 Web 请求生命周期中频繁执行。 --- ## 十、最常见的误区 ### 误区 1:`--full-static` 比 `--nano` 更轻 不一定。全静态包含完整 PHP、ZendVM、扩展、第三方库和 libc,体积通常最大。 Nano 关注的是移除 ZendVM 和未使用能力,两者优化目标完全不同。 ### 误区 2:`--php-builder` 生成的程序没有任何系统依赖 错误。它不依赖宿主 PHP,但仍依赖操作系统提供的底层库。需要零动态库依赖时,应 使用 full-static SDK。 ### 误区 3:Nano 编译时不需要 PHP 错误。构建机仍要用完整 PHP 执行 `tpc.php`、php-parser 和 Composer 依赖。只有最终 产物使用 Nano。 ### 误区 4:`mode: lib` 和 `mode: ext` 都是 `.so`,所以一样 文件后缀相同不代表 ABI 和生命周期相同: - `lib` 由普通原生宿主加载,并显式初始化 TypePHP 运行时; - `ext` 由 PHP SAPI 加载,使用 PHP 模块入口和每请求生命周期。 ### 误区 5:Windows `--nano` 已经不需要 PHP DLL 当前还不是。Windows 会执行 Nano 能力检查,但运行时仍是完整 PHP/PHPX DLL。 ### 误区 6:把 `sapi: cli` 写进 `php-builder` `sapi` 是顶层配置: ```yaml mode: bin sapi: cli entry: app.php php-builder: {} ``` `php-builder` 只描述“从源码构建私有 PHP”,不描述使用哪一种 SAPI。 --- ## 总结 TypePHP 的四种运行时方案可以用四句话概括: - **默认宿主机 libphp**:构建最快,但部署环境必须提供 ABI 匹配的 PHP/PHPX; - **`--php-builder`**:自己构建完整 PHP,不依赖宿主 PHP,但仍依赖系统底层库; - **`--full-static`**:使用专用 SDK,把完整 PHP、扩展和 libc 都装进一个 Linux ELF; - **`--nano`**:移除 ZendVM,直接编译精简运行时源码,适合原生应用和受控功能边界。 再配合产物类型: - `bin` 自己拥有进程入口; - `lib` 由原生宿主管理生命周期; - `ext` 由现有 PHP SAPI 按模块和请求生命周期加载。 选择时不要先问“哪个参数最强”,而要先回答三个问题:目标平台是什么、是否需要 完整 ZendVM、部署环境允许保留哪些动态依赖。答案确定以后,构建方案自然就确定了。 ## 技术社区 TypePHP 由识沃科技(Swoole 团队)主导研发。欢迎添加识沃客服微信,加入技术交流 群,与开发者直接交流、获取最新版本与构建指南。 
发布于2周前 · 2 次浏览 · 来自
分享
Rango-H
使用 TypePHP 编译一个项目时,经常会看到这些参数: ```text --nano --full-static --php-builder -m bin / -m lib / -m ext --sapi=embed / cli / fpm ``` 它们看起来都在决定“怎么编译”,但实际上描述的是三个不同问题: 1. **产物是什么**:可执行文件、动态库,还是 PHP 扩展; 2. **谁负责启动 PHP**:Embed、CLI、FPM,还是外部原生宿主; 3. **PHP 运行时从哪里来**:宿主机、源码现编、全静态 SDK,还是 PHP Nano 源码。 只要先把这三条轴分开,TypePHP 的构建方式就不再复杂。 本文将详细介绍四种运行时与链接方案: - 默认链接宿主机 `libphp.so`; - 使用 `--php-builder` 从 php-src 构建私有运行时; - 使用 `--full-static` 生成无动态库依赖的 Linux 程序; - 使用 `--nano` 生成不包含 ZendVM 的轻量原生产物。 还会说明 Android、iOS、macOS、Windows 原生应用分别支持到什么程度,以及 `bin`、`lib`、`ext` 到底有什么区别。 --- ## 一、先建立正确的心智模型 TypePHP 的核心配置可以拆成下面三层: | 层次 | 配置 | 回答的问题 | | --- | --- | --- | | 产物类型 | `mode: bin/lib/ext` | 最终生成可执行文件、共享库还是 PHP 扩展? | | 进程接口 | `sapi: embed/cli/fpm` | `bin` 可执行文件以哪一种 PHP SAPI 启动? | | 运行时来源 | 默认 / `--php-builder` / `--full-static` / `--nano` | PHP、PHPX 和底层库从哪里来? | 其中有两条必须牢记: - `cli`、`fpm` 是 **SAPI**,不是 mode;它们生成的仍然是 ELF、Mach-O 或 PE 可执行文件; - `--nano`、`--full-static` 和 `--php-builder` 不是三种产物,它们是在改变运行时 的组成与链接方式。 例如: ```yaml mode: bin sapi: cli entry: bin/console.php php-builder: {} ``` 这段配置的完整含义是: > 生成一个可执行文件;使用 PHP CLI SAPI 启动;PHP 运行时不取宿主机现成库, > 而是从 php-src 重新构建并静态链接。 --- ## 二、默认方案:链接宿主机 libphp 不添加 `--nano`、`--full-static` 或 `--php-builder` 时,TypePHP 使用完整 PHP 运行时。Linux 上通常是 `libphp.so`,macOS 上是 `libphp.dylib`,Windows 则通过 import library 链接工具包中的 PHP DLL。 最简单的程序: ```php <?php function main(int $argc, array $argv): void { echo "Hello TypePHP\n"; var_dump(PHP_VERSION, $argv); } ``` 直接编译: ```bash vendor/bin/tpc.php hello.php ./hello first second ``` 默认等价于: ```yaml mode: bin sapi: embed ``` ### 链接过程 以 Linux 为例,链接关系可以简化为: ```text TypePHP 生成的 .o + libphpx.so + libphp.so + GMP / MPFR / libstdc++ 等系统库 ↓ hello(ELF) ``` TypePHP 通常优先使用 PHPX 的共享库和 PHP Embed 共享库,并在 ELF/Mach-O 中写入 对应的运行时搜索路径。最终机器仍然要能找到这些共享库。 可以检查实际依赖: ```bash ldd ./hello # Linux otool -L ./hello # macOS dumpbin /dependents hello.exe # Windows ``` ### 这种方案的优点 - 编译最快,不需要重新构建 PHP; - 产物较小; - 使用完整 PHP、ZendVM 和宿主机已经准备好的扩展能力; - 很适合开发机、固定服务器和统一镜像环境。 ### 需要注意的问题 - 目标机器必须提供 ABI 匹配的 PHP/PHPX 库; - PHP 版本、ZTS/NTS、Debug/Release 和编译选项必须一致; - “机器上能执行 `php`”不代表存在 Embed SAPI 使用的 `libphp.so`;很多发行版只 安装 PHP CLI/FPM; - 复制单个可执行文件到另一台机器,可能因为缺少库或版本不一致而无法启动。 Linux/macOS 缺少 Embed 库时,在交互式终端中 TypePHP 会询问是否切换到 `php-builder`。在 CI 等非交互环境中,应显式添加 `--php-builder`,否则构建会停止。 ### `ext` 模式为什么不链接 libphp.so 这是一个很重要的例外。`mode: ext` 生成的是由现有 PHP 进程加载的扩展: ```text php-fpm / php-cli / Apache SAPI ↓ 已经拥有 ZendVM、全局表和内存管理器 typephp_demo.so ``` 在 Linux/macOS 上,扩展不应再链接一个 `libphp.so`,否则同一进程可能出现两套 ZendVM 全局状态。扩展中的 Zend/PHP 符号由加载它的 PHP SAPI 解析。 它仍然需要匹配的 PHP 头文件、ABI,以及进程级唯一的 PHPX 共享运行时。 --- ## 三、`--php-builder`:从 php-src 构建私有完整 PHP `--php-builder` 的目标是: > 不依赖宿主机现成的 `libphp.so`,从官方 php-src 构建一套项目可用的私有静态 > PHP 运行时。 最简单的用法: ```bash vendor/bin/tpc.php hello.php --php-builder ``` 不带值的 `--php-builder` 等价于空映射 `{}`。如果要指定扩展和 ZTS: ```bash vendor/bin/tpc.php project.yml \ --php-builder='extensions: [swoole, mongodb]; zts: on' ``` YAML 写法是: ```yaml name: myapp mode: bin sapi: embed php-builder: extensions: [swoole, mongodb] zts: on sources: - src ``` 请注意,`sapi` 不属于 `php-builder`。下面这种写法是错误的: ```yaml php-builder: sapi: cli # 错误 ``` 应该写成: ```yaml sapi: cli entry: bin/console.php php-builder: {} ``` ### 它构建了什么 第一次构建时,TypePHP 会: 1. 下载与 `php-version` 匹配的官方 PHP 源码包; 2. 从源码、YAML、Composer `ext-*` 依赖中收集所需扩展; 3. 为 php-src 自动生成 configure 参数; 4. 将 php-src 内置扩展和外部 PECL 扩展静态编入 PHP; 5. 构建目标 SAPI、`libphp.a` 和匹配的 `libphpx.a`; 6. 将生成的 TypePHP 模块链接进最终可执行文件。 官方 php-src 缓存不会被直接修改。需要注入 PECL 扩展时,会创建派生源码目录。 缓存位于: ```text ~/.typephp/ ├── archives/ # PHP、PECL 源码包 ├── src/ # 未修改的官方 php-src ├── pecl/ # PECL 解压目录 ├── php-builder-src/ # 注入外部扩展后的派生源码 └── php-builder/ # 构建好的私有运行时 ``` 类似下面的目录名: ```text php-8.5.10-e90c19d6ad02c94f ``` 末尾哈希是兼容性指纹,包含 PHP 补丁版本、SAPI、ZTS、扩展集合、系统、CPU 和 工具链,不包含项目路径。因此多个兼容项目可以复用同一套运行时,并不是每个项目 复制一份 PHP 源码。 ### Embed、CLI、FPM 三种 SAPI `php-builder` 同时支持 PHP 的三种启动方式: | SAPI | 用途 | 是否需要 `entry` | | --- | --- | --- | | `embed` | TypePHP 自己拥有 `main`,进程内嵌完整 PHP | 否 | | `cli` | 构建官方 PHP CLI 主程序,并静态注册 TypePHP 模块 | 是 | | `fpm` | 构建包含 TypePHP 模块的 PHP-FPM | 否 | CLI 示例: ```yaml name: worker mode: bin sapi: cli entry: bin/worker.php php-builder: extensions: [pcntl, sockets] zts: off sources: - src ``` `entry` 是 ZendVM 在 CLI 启动时执行的 PHP 主脚本;`sources` 中的业务代码仍然由 TypePHP AOT 编译。CLI 入口不需要定义 TypePHP Embed 模式的全局 `main()`。 FPM 示例: ```yaml name: myapp-fpm mode: bin sapi: fpm php-builder: extensions: [pdo, pdo_mysql, opcache] zts: off sources: - src ``` 也可以一次构建多个 SAPI。YAML 使用列表,命令行使用逗号分隔: ```yaml mode: bin sapi: [embed, cli, fpm] entry: bin/console.php php-builder: extensions: [] zts: on ``` ```bash vendor/bin/tpc.php project.yml \ --sapi=embed,cli,fpm \ --entry=bin/console.php \ --php-builder='extensions: []; zts: on' ``` 多目标构建会分别产生带 `-embed`、`-cli`、`-fpm` 后缀的程序。只要 SAPI 列表包含 `cli`,就必须提供 `entry`;若配置了 `entry` 却没有选择 `cli`,TypePHP 只会给出 warning 并忽略它。 Embed 与 CLI 程序通常在一次进程执行中只经历一次请求初始化和关闭。FPM 不同:它会 在常驻 Worker 中为每个 HTTP 请求反复执行 RINIT/RSHUTDOWN。即使模块已经静态注册, 也不能在这两个阶段加入大规模循环、全表遍历或其他昂贵工作。 下载 PHP 或 PECL 源码需要经过代理时,使用独立的全局参数 `--proxy`: ```bash vendor/bin/tpc.php project.yml --php-builder \ --proxy=http://127.0.0.1:7890 ``` `--proxy` 不属于 `php-builder`;其他由 TypePHP 发起的下载、上传等网络行为也应复用 这一代理设置。 ### 为什么它不是全静态 `php-builder` 会把 PHP、PHPX 和选中的 PHP 扩展静态编进可执行文件,但底层库仍由 操作系统提供,例如: - libc; - libxml2; - zlib; - OpenSSL; - SQLite; - 扩展依赖的其他系统库。 所以它解决的是“不要依赖宿主机 PHP”,不是“不要依赖任何动态库”。 ```bash ldd ./myapp ``` 仍然可能看到多项系统共享库,这是正常的。 ### 当前平台边界 - 支持 Linux、macOS; - 只适用于 `mode: bin`; - `cli` 和 `fpm` 强制依赖 `php-builder`; - Windows、Android、iOS 当前不使用 `php-builder`; - 编译器本身仍由宿主 PHP 执行,`php-builder` 替换的是目标程序运行时。 --- ## 四、`--full-static`:真正没有动态库依赖 `--full-static` 面向 Linux 可执行文件分发。它使用专用 SDK,把以下内容一起静态 链接进最终 ELF: - 完整 PHP 和 ZendVM; - PHPX; - PHP 扩展; - 第三方依赖; - C/C++ 运行时; - musl libc 和启动文件。 因此它和 `php-builder` 的根本区别是: | | `--php-builder` | `--full-static` | | --- | --- | --- | | PHP 来源 | 构建时从 php-src 编译 | 使用预制全静态 SDK | | PHP/ZendVM | 完整版 | 完整版 | | PHP 扩展 | 按项目需求构建 | 由 SDK 预先提供 | | 系统动态库 | 仍然依赖 | 目标是 0 个 | | libc | 操作系统提供 | SDK 内的 musl | | 适合场景 | 固定 Linux/macOS 系统,自带私有 PHP | 跨 Linux 发行版复制单文件运行 | 这里最容易产生误解: > **全静态不等于 Nano。** 全静态程序仍然包含完整 PHP 和 ZendVM,动态 PHP 能力与扩展能力取决于 SDK;它只 是在链接层面把所有库装进了一个 ELF。 ### 准备 SDK 从 PHPX Releases 下载与 CPU、PHP ABI 匹配的 full-static SDK,并确保目录结构为: ```text $PHPX_HOME/full-static/sdk/ ├── include/ └── lib/ ├── libphp.a ├── libphpx.a └── musl/ ├── crt1.o ├── crti.o └── crtn.o ``` 然后编译: ```bash export PHPX_HOME=/path/to/phpx vendor/bin/tpc.php hello.php --full-static --compiler=/usr/bin/clang ``` 全静态 SDK 中的 `libphp.a` 使用 musl,链接阶段也必须使用对应的 musl target 和 启动文件。TypePHP 会自动设置目标三元组、`-static` 和 SDK 内的启动文件路径。 当前 `--full-static` 需要能够生成 Linux musl 目标的原生 Clang。若显式传入的 `--compiler` 不支持对应 target,编译器会直接报错,而不会偷偷换成另一套工具链。 ### 验证是否真的全静态 ```bash file ./hello ldd ./hello readelf -dW ./hello | grep -E 'NEEDED|INTERP' ``` 预期结果是: - `file` 显示 `statically linked`; - `ldd` 显示“不是动态可执行文件”或等价信息; - `readelf` 没有 `NEEDED` 和动态解释器记录。 全静态产物通常明显大于普通程序,因为完整 PHP、扩展、libc 和第三方库都在同一个 文件里。它适合交付、离线环境、基础系统差异较大的服务器,但仍必须匹配 CPU 架构, 也仍然受 Linux 内核与系统调用兼容性约束。 ### 当前限制 - 只支持 `mode: bin`; - 不支持 `lib` 和 `ext`,共享库不能安全地再携带一套 libc; - 面向 Linux musl ELF,不用于 macOS、Windows、Android 或 iOS; - 依赖专用 full-static SDK,不能用普通 `libphp.a` 代替; - 不能和 `--nano` 组合; - 不应与 `--php-builder` 组合,两者是不同的运行时来源方案。 --- ## 五、`--nano`:不包含 ZendVM 的源码组合运行时 Nano 解决的是另一个问题: > 最终产物不链接完整 `libphp`,而是只把 PHP Nano、PHPX 和实际需要的运行时源码 > 编进应用。 安装 PHP Nano 1.0.2 或更高版本: ```bash composer require --dev swoole/typephp "swoole/php-nano:^1.0.2" ``` 编译: ```bash vendor/bin/tpc.php hello.php --nano ./hello ``` 在 Linux、macOS、Android、iOS 上,构建关系大致是: ```text TypePHP 生成的 C++ + PHP Nano 源码 + PHPX 源码(PHPX_NANO) + 被静态选择的 Composer 原生扩展 ↓ 原生 bin / lib ``` 最终产物不需要 `libphp.so`、`libphp.dylib`、`libphp.a` 或预编译 `libphpx`。 ### Nano 不是什么 Nano 不是把 `tpc.php` 自己编译成一个微型编译器。TypePHP 编译器需要动态执行 `nikic/php-parser` 等 Composer 包,所以 `tpc.php` 仍然运行在构建机的完整 PHP 上。 `--nano` 只作用于生成的目标程序。 Nano 也不等于操作系统层面的“全静态”。普通 Linux Nano 程序仍可能依赖 libc、 libstdc++ 或目标平台框架。它不依赖的是完整 PHP/PHPX 运行库。 因此: - `--full-static`:完整 PHP + ZendVM,链接层零动态库; - `--nano`:没有完整 PHP/ZendVM,但可能仍有操作系统动态依赖。 ### 能力边界 Nano 复用 PHP 的 `zval`、字符串、数组、对象、异常、GC 和一部分内置扩展源码,但 移除了 ZendVM 解释执行和不符合轻量原生模型的宿主能力。 不支持的典型能力包括: - `eval`、`include`、`require`; - Generator、Fiber、匿名类; - 动态加载 PHP 扩展; - 外部命令和进程 API; - socket、DNS、远程 stream; - 依赖动态 PHP 源码执行的框架机制。 Nano 更适合代码边界明确的服务、工具、嵌入式组件和原生 GUI 应用,而不是直接把 任意传统 PHP 框架原封不动搬进去。 ### `bin` 与 `lib` Nano 支持: - `mode: bin`:产物自己提供 `main`,调用 TypePHP 全局 `main()`; - `mode: lib`:不生成 `main`,由 Android JNI、Apple 桥或其他原生宿主初始化; - 不支持 `mode: ext`。 共享库配置示例: ```yaml name: my_runtime mode: lib output: build/libmy_runtime.so cxx-std: c++17 sources: - src - bridge ``` ```bash vendor/bin/tpc.php project.yml --nano ``` 宿主通过 `typephp_runtime.h` 中的项目级 C ABI 初始化运行时: ```cpp #include <typephp_runtime.h> TYPEPHP_RUNTIME_INIT_FUNCTION(my_runtime); TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(my_runtime); if (TYPEPHP_RUNTIME_INIT(my_runtime)(argc, argv) != 0) { return 1; } // 此后可以调用 TypePHP 导出的函数 TYPEPHP_RUNTIME_SHUTDOWN(my_runtime)(); ``` 初始化应覆盖整个进程或原生库生命周期,不能在每次按钮点击、JNI 调用或业务函数 执行前重复初始化。Nano 的 MINIT/RINIT 在初始化时执行一次,RSHUTDOWN/MSHUTDOWN 在关闭时执行一次。 ### Windows 上的特殊情况 当前 php-nano 源码组合后端不支持 Windows。Windows 上使用 `--nano` 时: - 会应用 Nano 的语法和能力限制; - 仍然链接完整的 PHP/PHPX DLL; - 产物并不是“不依赖 PHP DLL”的真正 Nano 程序。 所以如果目标是缩小 Windows 运行时或彻底移除 ZendVM,当前版本还做不到;Windows 上的 `--nano` 更准确地说是 Nano policy 模式。 --- ## 六、`bin`、`lib`、`ext` 到底有什么区别 ### 1. `mode: bin`:拥有进程入口 `bin` 生成平台可执行文件: | 平台 | 产物 | | --- | --- | | Linux | ELF 可执行文件 | | macOS/iOS | Mach-O 可执行文件 | | Windows | PE `.exe` | | Android | Native executable,普通 App 通常不直接使用 | 默认 Embed 和 Nano `bin` 都要求 TypePHP 源码中存在全局 `main()`。CLI SAPI 是例外, 它通过 `entry` 指定由 ZendVM 执行的入口脚本。 只有 `bin` 可以选择 `sapi`,也只有 `bin` 支持 `php-builder` 和 `--full-static`。 ### 2. `mode: lib`:由外部原生宿主加载 `lib` 生成 `.so`、`.dylib` 或 `.dll`,不提供操作系统 `main`。典型用途是: - Android App 的 JNI 共享库; - 被 Objective-C++、Swift、C/C++ 程序调用; - 被另一个 TypePHP 项目链接; - 把一组 TypePHP 函数作为原生 ABI 发布。 普通模式的 `lib` 仍依赖完整 PHP/PHPX 运行时;Nano `lib` 则直接包含 Nano/PHPX 源码。TypePHP 会生成公开函数声明与导入 stub,并导出项目级运行时初始化/关闭函数。 ### 3. `mode: ext`:由现有 PHP SAPI 加载 `ext` 生成 PHP 扩展:Linux/macOS 通常是 `.so`/bundle,Windows 是 `.dll`。它没有 自己的 `main`,也不应该启动第二套 PHP 运行时。 扩展由 PHP CLI、PHP-FPM、Apache 等现有进程加载,因此: - MINIT/MSHUTDOWN 对应 PHP 模块生命周期; - RINIT/RSHUTDOWN 会随 PHP 请求反复执行; - RINIT/RSHUTDOWN 中不能放大量循环、全表遍历或昂贵初始化; - 进程级只读元数据和字面量应尽可能在 MINIT 阶段一次性建立。 Nano 不支持 `mode: ext`,因为 Nano 本身就是运行时,不存在“加载进另一套 Nano PHP” 的动态扩展场景。 ### 三种“扩展”不要混淆 TypePHP 中“扩展”一词可能出现在三个位置: | 名称 | 实际含义 | | --- | --- | | `mode: ext` | 生成一个可被外部 PHP 加载的 TypePHP 扩展 | | `php-builder.extensions` | 把 PHP/PECL 扩展静态编进私有完整 PHP 运行时 | | Nano Composer 原生扩展 | 把符合 Nano 能力边界的扩展源码静态编进 Nano `bin/lib` | 后两者都不是 `mode: ext`。 --- ## 七、Android、iOS、macOS、Windows 支持矩阵 ### 运行时方案 | 运行时方案 | Linux | macOS | Windows | Android | iOS | | --- | --- | --- | --- | --- | --- | | 默认完整运行时 | ✅ 宿主 `libphp.so` | ✅ 宿主 `libphp.dylib` | ✅ 工具包 PHP/PHPX DLL | ✅ 目标平台 SDK | ✅ 目标平台 SDK | | `--php-builder` | ✅ | ✅ | ❌ | ❌ | ❌ | | `--full-static` | ✅ Linux musl ELF | ❌ | ❌ | ❌ | ❌ | | `--nano` 源码组合 | ✅ | ✅ | ⚠️ 仅 policy,仍用 DLL | ✅ | ✅ | ### 原生应用形态 | 平台 | 推荐组合 | 说明 | | --- | --- | --- | | Android | `--nano + mode: lib` | 生成 JNI `.so`,Java Activity 只负责生命周期和 View 桥接;不需要 Android `libphp.a/libphpx.a` | | iPhone 真机 | `--nano + mode: bin` | Xcode iPhoneOS 工具链生成 Mach-O;仍需证书与 Provisioning Profile | | iOS Simulator | `--nano + mode: bin` | Apple silicon 可直接编译 simulator 目标;不需要独立 PHP/PHPX 静态 SDK | | macOS AppKit | 默认 Embed 或 `--nano + mode: bin` | 默认方式依赖宿主库;Nano 直接编译运行时源码 | | Windows GUI | 默认完整运行时 `mode: bin/lib` | 使用工具包 DLL,可配合原生 Win32/C++ 桥和 `--no-console`;目前没有真正 DLL-free Nano | Android/iOS 的普通非 Nano 构建也可以使用,但需要分别准备与目标架构、PHP ABI 和 工具链匹配的 `libphp.a`、`libphpx.a` 及头文件 SDK。宿主机 Linux/macOS 的静态库 不能直接拿来交叉链接移动端。 移动应用通常选择 `bin` 或 `lib`。`ext` 需要一个先存在的 PHP SAPI 负责加载,在 Android/iOS App 中一般没有这种宿主,因此不是移动原生应用的部署方式。 --- ## 八、四种方案放在一起比较 | 对比项 | 宿主机 libphp | `--php-builder` | `--full-static` | `--nano` | | --- | --- | --- | --- | --- | | 完整 ZendVM | ✅ | ✅ | ✅ | ❌ | | 依赖宿主 PHP 运行库 | ✅ | ❌ | ❌ | 非 Windows:❌ | | 依赖系统动态库 | ✅ | ✅ | ❌ | 通常 ✅ | | PHP 扩展来源 | 宿主 PHP | 自动收集后源码构建 | SDK 预置 | Composer 源码静态选择 | | 首次构建速度 | 最快 | 慢,需要编译 PHP | 快,直接使用 SDK | 慢,需要编译 Nano/PHPX 源码 | | 增量构建 | 快 | 复用私有运行时缓存 | 复用 SDK 与对象缓存 | 复用 Nano 对象缓存 | | 产物体积 | 小 | 中到大 | 最大 | 按使用能力裁剪 | | `bin` | ✅ | ✅ | ✅ | ✅ | | `lib` | ✅ | ❌ | ❌ | ✅ | | `ext` | ✅ | ❌ | ❌ | ❌ | | 主要平台 | Linux/macOS/Windows | Linux/macOS | Linux | Linux/macOS/Android/iOS;Windows 仅 policy | 表中的“宿主机 libphp”在移动端应理解为“目标平台完整 PHP SDK”,不能使用构建机 自己的库。 --- ## 九、应该如何选择 ### 场景 1:开发机或固定服务器 选择默认宿主机 `libphp.so`: ```bash vendor/bin/tpc.php app.php ``` 优点是最快、最简单。只要部署机器和构建机器的 PHP/PHPX ABI 一致即可。 ### 场景 2:目标机没有 Embed PHP,但允许依赖系统库 选择 `--php-builder`: ```bash vendor/bin/tpc.php app.php \ --php-builder='extensions: [curl, opcache]; zts: off' ``` 它特别适合想要私有 PHP 版本、固定扩展集合,又不想自己维护 SDK 的 Linux/macOS 项目。 ### 场景 3:一个文件复制到不同 Linux 发行版运行 选择 `--full-static`: ```bash vendor/bin/tpc.php app.php --full-static --compiler=/usr/bin/clang ``` 前提是准备好匹配架构和 PHP ABI 的 full-static SDK。 ### 场景 4:移动端、桌面 GUI、嵌入式原生宿主 优先考虑 `--nano`: ```bash vendor/bin/tpc.php project.yml --nano ``` Android 通常使用 `mode: lib`,Apple 桌面/移动示例可以使用 `mode: bin`。如果业务 依赖动态 Composer PHP 代码、网络、进程或 ZendVM 特性,则改用目标平台完整 SDK。 ### 场景 5:把 TypePHP 代码交给现有 PHP-FPM 加载 选择 `mode: ext`: ```bash vendor/bin/tpc.php extension.yml -m ext -o my_extension ``` 此时要特别审查 RINIT/RSHUTDOWN 性能,因为它们会在 Web 请求生命周期中频繁执行。 --- ## 十、最常见的误区 ### 误区 1:`--full-static` 比 `--nano` 更轻 不一定。全静态包含完整 PHP、ZendVM、扩展、第三方库和 libc,体积通常最大。 Nano 关注的是移除 ZendVM 和未使用能力,两者优化目标完全不同。 ### 误区 2:`--php-builder` 生成的程序没有任何系统依赖 错误。它不依赖宿主 PHP,但仍依赖操作系统提供的底层库。需要零动态库依赖时,应 使用 full-static SDK。 ### 误区 3:Nano 编译时不需要 PHP 错误。构建机仍要用完整 PHP 执行 `tpc.php`、php-parser 和 Composer 依赖。只有最终 产物使用 Nano。 ### 误区 4:`mode: lib` 和 `mode: ext` 都是 `.so`,所以一样 文件后缀相同不代表 ABI 和生命周期相同: - `lib` 由普通原生宿主加载,并显式初始化 TypePHP 运行时; - `ext` 由 PHP SAPI 加载,使用 PHP 模块入口和每请求生命周期。 ### 误区 5:Windows `--nano` 已经不需要 PHP DLL 当前还不是。Windows 会执行 Nano 能力检查,但运行时仍是完整 PHP/PHPX DLL。 ### 误区 6:把 `sapi: cli` 写进 `php-builder` `sapi` 是顶层配置: ```yaml mode: bin sapi: cli entry: app.php php-builder: {} ``` `php-builder` 只描述“从源码构建私有 PHP”,不描述使用哪一种 SAPI。 --- ## 总结 TypePHP 的四种运行时方案可以用四句话概括: - **默认宿主机 libphp**:构建最快,但部署环境必须提供 ABI 匹配的 PHP/PHPX; - **`--php-builder`**:自己构建完整 PHP,不依赖宿主 PHP,但仍依赖系统底层库; - **`--full-static`**:使用专用 SDK,把完整 PHP、扩展和 libc 都装进一个 Linux ELF; - **`--nano`**:移除 ZendVM,直接编译精简运行时源码,适合原生应用和受控功能边界。 再配合产物类型: - `bin` 自己拥有进程入口; - `lib` 由原生宿主管理生命周期; - `ext` 由现有 PHP SAPI 按模块和请求生命周期加载。 选择时不要先问“哪个参数最强”,而要先回答三个问题:目标平台是什么、是否需要 完整 ZendVM、部署环境允许保留哪些动态依赖。答案确定以后,构建方案自然就确定了。 ## 技术社区 TypePHP 由识沃科技(Swoole 团队)主导研发。欢迎添加识沃客服微信,加入技术交流 群,与开发者直接交流、获取最新版本与构建指南。 
赞
0
收藏
提问
分享
讨论
建议
公告
开发框架
TypePHP
CodeGalaxy
登录
后参与评论
评论
还没有评论!