返回

FrankenPHP 集成 Zend 扩展:自定义扩展加载指南

php

FrankenPHP 与 Zend 扩展

FrankenPHP 提供了一种新的 PHP 应用部署方式,它将 PHP 运行时嵌入到一个 Go 可执行文件中。对于习惯了 PHP-FPM 和传统 web 服务器的开发者而言,理解如何集成自己的 Zend 扩展可能会有点挑战。本篇文章将探讨如何将自定义的 PHP Zend 扩展引入 FrankenPHP 的环境。

问题:自定义 Zend 扩展未被加载

开发者使用自己的 PHP Zend 扩展,该扩展在 PHP-CLI 和 PHP-FPM 中工作良好。但是,在 FrankenPHP 中运行时,该扩展无法加载,这会导致应用程序的功能缺失或者发生意料之外的错误。

这其中,有两个方面因素值得思考:

  1. 编译时依赖: FrankenPHP 构建时,默认只包含一部分 PHP 扩展,它并不会自动识别或包含用户的自定义 Zend 扩展,需要进行特别配置。
  2. 动态链接: 传统的 PHP 扩展通过 .so 文件动态链接加载。但 FrankenPHP 将 PHP 运行时直接编译进了可执行文件,因此,动态加载的思路可能无法直接应用。

解决方案一:在 FrankenPHP 构建时链接扩展

这种方式在 FrankenPHP 的构建阶段就将自定义 Zend 扩展编译进 Go 可执行文件。这保证了扩展随应用程序一起分发,消除了运行时的依赖问题。

步骤:

  1. 准备编译环境: 确保你已安装 Go, PHP 开发环境 (包含 php-config),以及你的 Zend 扩展所需的编译工具和库。

  2. 修改构建命令: 在 FrankenPHP 的构建过程中,使用 CGO_CFLAGSCGO_LDFLAGS 环境变量来指定你的 Zend 扩展的编译和链接选项。例如:

    CGO_CFLAGS="$(php-config --includes)" CGO_LDFLAGS="$(php-config --ldflags) -L/path/to/your/extension/lib -lmyextension" go build -tags embed
    
  • php-config --includes: 提供 PHP 相关的头文件路径。
  • php-config --ldflags: 提供 PHP 相关的链接选项。
  • -L/path/to/your/extension/lib: 指定你的扩展库文件 .so.a 所在目录,如果使用动态链接,必须是.so所在路径,且需要能通过ldd命令追踪到。如果编译为静态库,使用.a结尾。
  • -lmyextension: 链接名为 myextension 的库,实际替换为你自己库的名称,需要不包含lib前缀和文件后缀
  • -tags embed 启用静态资源嵌入
如果编译时提示 `找不到头文件`,或者`链接不到`之类的报错信息,则需要在编译指令中,`CGO_CFLAGS` 加入头文件的绝对路径; `CGO_LDFLAGS` 中加入库文件的绝对路径,确认这些目录的确是编译和运行所需。
  1. 构建: 运行修改后的 go build 命令生成 FrankenPHP 可执行文件。
  2. 验证: 启动 FrankenPHP 并检查 PHP 配置,确认扩展已被正确加载。可以使用 phpinfo() 函数,检查加载的扩展列表。
 <?php
 phpinfo();

此方法避免了运行时依赖,且因为在编译期已经做完,在运行时无需担心任何依赖项,效率最高。但是其缺点是增加了二进制文件大小。且每修改一次扩展代码需要重新编译整个FrankenPHP项目。

解决方案二:使用共享对象库 (动态加载,慎用)

与传统 PHP 应用相似,你也可以尝试使用动态链接方式加载你的 Zend 扩展。但值得警惕的是 ,这种方法不如将扩展静态链接到 FrankenPHP 二进制文件中那么简单,并不能保证可靠,不建议生产使用,可能在升级FrankenPHP之后无法使用。

步骤:

  1. 构建动态库: 使用 phpizephp-config 将你的 Zend 扩展编译为动态链接库(.so文件)。
  2. 配置 PHP.ini:
    创建并编辑 php.ini 或 专门的 ini 配置项, 使用 extension=/path/to/your/extension.so 加载扩展. 需要注意路径的绝对性,最好复制到容器中的相对稳定位置,例如/etc/php/conf.d
  3. 容器运行时指定挂载 ,在docker run时指定-v 参数,确保该.so能从host主机正确的拷贝进入容器中的配置目录。例如 -v /home/my_user/my_project/extension:/etc/php/conf.d
  4. 构建和运行 :构建 frankenphp, 使用容器的方式进行测试,并使用phpinfo() 确认。
  5. 权限问题 ,要注意运行PHP时所在的账号和当前 .so 的访问权限,一般保持 644 权限即可。
    此种方法最大的优点是当更新php extension时无需更新整个FrankenPHP二进制,而是只需要更新.so文件和重启容器。
    此方法的缺点是不够稳定可靠,必须严格确认运行php的账号拥有extension=/path/to/your/extension.so中so文件 的读取权限, 路径需要保证容器的路径存在。

安全注意事项

  • 扩展代码审查: 确保你自定义的 Zend 扩展代码经过严格的安全审查,避免潜在的安全漏洞。特别是当接收来自网络输入时,要小心处理,防止代码注入或其他安全问题。
  • 更新依赖: 经常更新你的 Zend 扩展依赖的库(例如:mpfr),修复已知的安全漏洞。
  • 隔离: 使用 docker 等容器化技术部署应用时,确保 PHP 运行时和 host 文件系统有一定的隔离,防止不必要的系统风险。

通过这两种方法,可以有效地解决 FrankenPHP 集成自定义 Zend 扩展的问题,确保应用的功能和性能达到最佳状态。选用哪种方案主要取决于具体的使用场景,比如是否频繁修改扩展,是否对包大小敏感等,请综合权衡后再做决策。