首页
/ Protocol Buffers PHP 双实现详解:纯 PHP 包与 C 扩展的安装、使用与测试实战

Protocol Buffers PHP 双实现详解:纯 PHP 包与 C 扩展的安装、使用与测试实战

2026-09-06 09:28:23作者:咎岭娴Homer

Protocol Buffers(protobuf)仓库在 php/ 目录下同时提供了 PHP 消息编解码的两种运行时实现:一个纯 PHP 包(php/src)和一个原生 C 扩展(php/ext)。两者提供完全相同的运行时 API 并共享同一套生成的代码,意味着同一条 .proto 定义无需重新生成即可在两种实现之间切换。读完本文,你将掌握 C 扩展的源码构建与 PECL 安装流程、composer 包的安装方式、protoc --php_out 的代码生成方法,以及如何在仓库内用 Bazel + composer + PHPUnit 对两套实现分别跑通测试。

一、双实现的定位:同一 API,两种性能取舍

php/README.md 开宗明义:

  • 纯 PHP 包(对应 php/src):面向更广泛的 PHP 平台提供可用性,无需编译,跨平台成本低;
  • C 原生扩展(对应 php/ext):面向更高性能场景,通过 PECL/源码编译安装。

两种实现都基于 protoc 生成的 PHP 代码来定义 message 与 enum 类型,且共享同一份生成代码——当需要在纯 PHP 与 C 扩展之间切换时,不需要重新执行代码生成。这一点在 php/src/Google/Protobuf/Internal 下的类结构中可以印证:纯 PHP 实现以 Message 为所有消息类的父类(见 Message.php),而 C 扩展在 def.c 中通过 Zend 对象接口(zend_class_entry、对象 handler)直接实现同名类,两套类名、方法签名保持一致,从而做到运行时 API 对齐。

README 还特别强调:php/ 目录内的构建流程只负责安装扩展或包本身,要获得 PHP 代码生成功能,必须另外安装 protoc 编译器(参见仓库根目录 README.md 中关于 protoc 的说明)。

关于版本支持,README 指出支持的 PHP 语言版本及其支持级别变化策略需参考外部“Supported PHP versions”文档(Google Cloud PHP 的官方支持矩阵,此处不展开外链)。从仓库当前配置看,纯 PHP 包在 composer.json 中声明了硬性要求:

"require": {
  "php": ">=8.2.0"
}

即当前仓库版本的 PHP 实现要求 PHP 8.2 及以上,开发测试则依赖 phpunit/phpunit >=11.5.50 <12.0.0

二、C 扩展安装

2.1 前置工具

README 列出的构建前置工具为:gcclibtoolmakepearpeclphpize。在 Ubuntu 上一键安装:

sudo apt-get install -y php-pear php-dev libtool make gcc

其他平台请使用对应的包管理器先装好上述工具再执行后续步骤。

2.2 从源码构建(Building extension)

README 给出的经典流程是:

cd ext/google/protobuf
pear package
sudo pecl install protobuf-{VERSION}.tgz

对应到当前仓库,C 扩展源码位于 php/ext/google/protobuf,其中包含完整的 PECL 打包材料:config.m4(GNU 平台构建脚本)、config.w32(Windows 构建脚本)、generate_package_xml.shtemplate_package.xml(打包元数据),以及核心的 C 源文件 protobuf.cdef.cmessage.carena.cconvert.cmap.c 和基于 upb 运行时的 php-upb.c

值得注意的是,仓库测试用的构建脚本 tests/compile_extension.sh 展示了更贴合日常开发的完整构建链路,可视为源码构建的“可运行版”:

  1. 先把 UTF-8 校验所需的 third_party/utf8_range 拷贝进 ext/google/protobuf/third_party/(与发布到 PECL 时相同);
  2. 通过 phpize 生成 configure,并用 --with-php-config=$(which php-config) 指向当前 PHP 解释器;非 release 模式额外加上 CFLAGS=-g -O0 -Wall -DPBPHP_ENABLE_ASSERTS 以便调试断言;
  3. sha256sum $(which php) 加上 configure 参数生成“指纹”写入 BUILD_STAMP,只有解释器或构建参数变化时才重新执行 phpize --clean && phpize && ./configure,避免每次全量重配;
  4. 最后 make -j8 编译,并用 make -j8 test 跑扩展自带的 .phpt 测试(如 ext/google/protobuf/tests/unnecessary_zval.phpt)。

这套指纹化机制意味着:在同一台机器上切换测试轮次时,构建系统会自动判断是否需要重新 configure,值得在自建 CI 时借鉴。

2.3 从 PECL 安装

protobuf 每次发版时会把扩展上传到 PECL,直接安装预打包版本即可:

sudo pecl install protobuf-{VERSION}

发版侧的实现可见 php/release.sh:它把 php/srccomposer.json.dist 同步到独立的 protobuf-php 发行仓库并打版本 tag——这也解释了为什么发布用的 composer 清单是 composer.json.dist 而非仓库根上的 composer.json(后者额外包含测试脚本与开发依赖)。

三、纯 PHP 包:composer 安装

README 的说明非常直接:在项目的 composer.jsonrequire 段加入 google/protobuf 即可。

composer.json 可以看到包的完整定义:

  • 包名为 google/protobuf,类型 library,许可 BSD-3-Clause;
  • 自动加载采用 PSR-4 映射:
    • Google\Protobuf\src/Google/Protobuf
    • GPBMetadata\Google\Protobuf\src/GPBMetadata/Google/Protobuf

其中 GPBMetadata 命名空间存放各 well-known proto 文件的元数据初始化类(如 Timestamp.phpDuration.phpStruct.phpGPBEmpty.php 等,见 php/src/GPBMetadata/Google/Protobuf),生成的消息类正是通过它们向全局 DescriptorPool 注册文件描述符。

另外两点与 README 呼应的细节:

  • README 提到纯 PHP 实现需要安装 bcmath 扩展。composer.json 中以 suggest 形式声明了 ext-bcmath,并注明其用途是“支持 JSON 反序列化”。也就是说 bcmath 是 JSON 编解码路径上的依赖,缺失时 JSON 相关功能可能不可用;
  • 包的 require 已收紧到 php >= 8.2.0,低于此版本的项目无法使用当前版本的包。

四、protoc 生成 PHP 代码

无论装了 C 扩展还是 composer 包,若要从 .proto 文件生成 PHP 代码,还需要安装 protoc(安装方式见仓库主 README.md)。最新版 protoc 支持 --php_out 选项:

protoc --php_out=out_dir test.proto

仓库自身正是这样生成测试代码的。php/generate_test_protos.sh 的工作流程完整展示了生成命令的实际用法:

# 优先使用仓库内已构建的 protoc,否则用 Bazel 构建 :protoc
find php/tests/proto -type f -name "*.proto" | \
  xargs $PROTOC --php_out=php/tmp -Isrc -Iphp/tests

关键参数与技巧:

  • -Isrc -Iphp/tests:把 src 目录(存放 google/protobuf/*.proto 标准库 proto)加入 include 路径,使测试 proto 能 import 标准定义;
  • --php_out=php/tmp:生成物统一落到 php/tmp,并在 composer.jsonautoload-dev 中通过 "": "tmp" 直接映射为类路径;
  • 增量优化:若 php/tmp 已存在且没有比 php/tests/proto$PROTOC 更新的文件,脚本直接跳过 protoc,避免重复生成;
  • --aggregate_metadata=foo#bar:php/tmp:聚合模式选项,可把多个 proto 文件的元数据合并到少量 metadata 类中(测试脚本提供了 aggregate_metadata_test 验证)。

测试用 proto 集合位于 php/tests/proto,覆盖 php 命名空间、前缀、保留字大小写、特殊字符、wrapper setter 等生成器边界场景(如 test_php_namespace.prototest_reserved_message_lower.prototest_special_characters.proto),这些正是验证 --php_out 输出正确性的真实用例。

五、已知问题清单(Known Issues)

README 明确列出了当前实现的已知局限,工程选型时应逐条评估:

  • 缺少对 well-known types 的原生支持;
  • 不支持 proto2;
  • 未提供清空/拷贝消息(clear/copy)的 API;
  • 未提供基于 stream 的编解码 API;
  • map 字段在存在循环引用时可能无法被垃圾回收;
  • C 扩展中的消息没有 debug 信息;
  • 未测试 HHVM;
  • C 扩展未在 Windows、macOS、PHP 7.0 上测试;
  • 消息名不能为 Empty

六、开发测试实战:如何分别验证两套实现

README 的 Development 章节给出了“先测原生 PHP、再测 C 扩展”的顺序。下面将其与仓库中当前实际存在的脚本和 composer 脚本对齐说明。

6.1 测试纯 PHP 实现

README 原始步骤(Linux):

# 安装依赖
apt-get install bazel composer php-dev

# 获取 protobuf 源码
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf

# 构建 protoc
bazel build :protoc

# 测试原生 php
cd php
composer install
composer test

其中 composer testcomposer.json 中的定义是:

"test": "./generate_test_protos.sh && vendor/bin/phpunit tests"

即“先生成测试 proto,再对 tests 目录跑 PHPUnit”——生成的测试代码直接落在 php/tmp 并被 dev 自动加载,无需手工配置 include 路径。

6.2 测试 C 扩展

README 给出的老流程是先 cd tests && ./test.sh 5.6(按系统安装的 PHP 运行时版本选择 5.5/5.6/7.x 及对应 zts 变体,并可用 ./gdb_test.sh 用 gdb 调试扩展)。在当前仓库中,C 扩展测试已被整合进 composer 脚本:

"test_c": "./generate_test_protos.sh && ./tests/compile_extension.sh \
  && php -dextension=ext/google/protobuf/modules/protobuf.so \
     vendor/bin/phpunit --bootstrap tests/force_c_ext.php tests"

三个环节各司其职:

  1. generate_test_protos.sh 生成测试代码(与纯 PHP 测试共享);
  2. tests/compile_extension.sh 完成 phpize → configure → make → make test 的完整构建(即第二节 2.2 中解析过的流程),产物为 ext/google/protobuf/modules/protobuf.so
  3. php -dextension=...protobuf.so 显式加载刚编译出的扩展,并以 tests/force_c_ext.php 作为 PHPUnit bootstrap 强制使用 C 扩展实现,从而与 composer test 的纯 PHP 跑法形成对照。

另外仓库还保留了两套更深入的调试手段,与 README 提到的 gdb 调试思路一致:

  • composer test_valgrind:在 ZEND_DONT_UNLOAD_MODULES=1 USE_VALGRIND=1 USE_ZEND_ALLOC=0 环境下以 valgrind --leak-check=full 运行同一套测试,用于排查内存泄漏;
  • tests/gdb_test.shtests/memory_leak_test.sh / multirequest.sh:分别用于 gdb 交互调试与多请求/内存泄漏场景验证。

七、小结:选型与落地要点

  1. 平台优先选纯 PHP,性能优先选 C 扩展:二者 API 相同、生成代码可复用,切换成本仅为“换加载方式”,无需重新 protoc
  2. 版本前提:当前仓库要求 PHP ≥ 8.2;纯 PHP 路径建议安装 ext-bcmath 以支持 JSON 反序列化;
  3. 生成代码与运行时解耦--php_out 生成的类只依赖 Google\Protobuf\Internal 运行时与 GPBMetadata 元数据,-Isrc 保证 well-known proto 可被导入;
  4. 验证闭环:日常用 composer test 验纯 PHP、composer test_c 验 C 扩展,需要深挖时用 valgrind 与 gdb 脚本,全部入口都已在 php/composer.jsonscripts 中定义好。
登录后查看全文
热门项目推荐
相关项目推荐