麻辣小媳妇
93.57 MB · 2025-12-16
前阵子做的一个直播弹幕的机器人,其中有一部分上游数据是通过 Protobuf 返回的。几个朋友问我怎么处理,但我发现大家对「PHP 解析 Protobuf」这件事多少有点迷糊。确实,PHP 处理 Protobuf 的资料不多,而且踩坑成本不算低。
这篇文章不打算科普什么,也没有推荐任何技术栈的意思,就是把我自己摸索的过程整理出来,给遇到类似问题的人一个参考。
很多人第一次接触它时,会把它和 JSON、XML 放在一起理解,但 Protobuf 并不是“另一个 JSON”。它是一种 基于 Schema 的二进制数据格式,本质上由两个部分组成:
.proto:数据结构的描述文件(类似字典).proto 规则编码出来的数据Google 发明它的原因大致是:
于是有了 Protobuf:数据格式紧凑、序列化速度快、跨语言支持也强。
它不是为了可读性,而是为了性能。
PHP 能解析 Protobuf,但体验不如其他语言。原因有几个,简单列一下:
像 Go、Python、Java 这类语言可以依靠 descriptor 动态解析 Protobuf 数据结构,甚至可以在运行期处理未知结构。
PHP 目前做不到,没有暴露那一套 API。
所以 PHP 必须依赖 .proto 文件,并且必须提前用 protoc 生成对应的 PHP 类。
google/protobuf 的 PHP 扩展只提供:
其他高级能力基本没有。
PHP 的常见使用场景偏 Web,因此处理二进制协议并不是重点。
在一些服务里,上游服务已经定死使用 Protobuf;或者 PHP 服务只是边缘网关,需要解析一次再转发。
在这种情况下,只有硬着头皮支持。
如果是自己的项目,并没有强约束,其实 JSON 足够了。
我自己在服务器上没有安装 Protobuf 扩展,而是采用更常见的一种方式:
protoc.proto 文件生成 PHP 类google/protobuf 包即可完成解析composer require google/protobuf
这是 PHP 解析 Protobuf 所需的唯一运行时依赖。
protoc 是官方编译器,用于把 .proto 文件生成各种语言的类(包括 PHP)。
下载地址:
github.com/protocolbuf…
选择对应平台的压缩包,解压后把 protoc 放到 PATH 中即可。
验证是否安装成功:
protoc --version
.proto 文件可以简单理解为“数据结构的一份字典”。
因为 Protobuf 的二进制格式里没有字段名,只有字段编号(tag)。
例如:
field #1: 123
field #2: "Alice"
你不知道 #1 是 id 还是 age,也不知道 #2 是 name 还是别的东西。
所以必须依靠 .proto 文件才能解码。
我本地的命令大致如下:
protoc --php_out=./protobuf
--proto_path=./protobuf
xxx.proto
含义如下:
--php_out:生成的 PHP 文件存放位置--proto_path:寻找 .proto 的目录protoc 会根据 .proto 内容生成一堆 PHP 类,每个 message 对应一个 PHP 类,最终这些类会继承:
GoogleProtobufInternalMessage
序列化、反序列化功能都来自这个基类。
如果你希望通过命名空间加载生成的类,可以在 composer.json 中加一条:
"autoload": {
"psr-4": {
"Proto\": "protobuf/"
}
}
然后执行:
composer dumpautoload
解析的核心方法是:
$msg->mergeFromString($binary)
读完后,数据结构会自动填充在 message 对象里。
序列化对应的方法是:
$binary = $msg->serializeToString();
得到的就是一段 protobuf 二进制字符串,可以直接发送到网络或写入文件。
创建一个项目,目录结构如下:
.
├── protobuf
│ └── TEST_USER_INFO.proto
└── test.php
syntax = "proto3";
option php_namespace = "TestUserInfo";
message User {
int32 id = 1;
string name = 2;
}
<?php
require __DIR__ . '/vendor/autoload.php';
use TestUserInfoUser;
$u1 = new User();
$u1->setId(7);
$u1->setName("PHP Encode Test");
$bin = $u1->serializeToString();
$u2 = new User();
$u2->mergeFromString($bin);
var_dump([
'原始数据' => bin2hex($bin),
'id' => $u2->getId(),
'name' => $u2->getName(),
]);
composer require google/protobuf
protoc --php_out=./protobuf --proto_path=./protobuf TEST_USER_INFO.proto
这会在 protobuf/ 目录下生成 PHP 类文件,供 PHP 使用。
在 composer.json 中增加命名空间映射,例如:
{
"require": {
"google/protobuf": "^4.33"
},
"autoload": {
"psr-4": {
"GPBMetadata\": "protobuf/GPBMetadata",
"TestUserInfo\": "protobuf/TestUserInfo"
}
}
}
然后重新加载 Composer 自动加载:
composer clear-cache && composer dump-autoload -o
php test.php
运行后,你会看到序列化再反序列化的数据被正确输出,证明 PHP 成功处理了 Protobuf 数据。
PHP 解析 Protobuf 的体验确实不算好,但能用,并且在某些需要兼容上游服务的场景里还是必须用。
如果你也正在处理类似的数据,希望这篇文章能帮你少踩点坑。
如果感觉文章里哪部分还没说清楚,欢迎继续交流。