很久之前,写PHP的时候,使用 Protobuf 做了聊天APP, 游戏服务器。 那个时候还用的是protobuf 2.5。
看了下proto3的语法,来测试下:

服务器环境 与 protoc 版本:

# cat /etc/redhat-release 
    CentOS Linux release 7.8.2003 (Core)

# protoc --version
    libprotoc 3.11.4

一、安装 PHP 的 Protocol Buffers 扩展:

1.1 默认安装最新版本

# pecl install protobuf

1.2 指定版本号安装:

# pecl install protobuf-{VERSION}

1.3 查看扩展是否已安装:

# php -m | grep protobuf
protobuf

1.4 查看protobuf扩展的版本信息:

# php --ri protobuf

protobuf

Version => 3.13.0

Directive => Local Value => Master Value
protobuf.keep_descriptor_pool_after_request => 0 => 0

二、编写编译proto文件:

2.1 编写proto文件:

# cat pack.proto

syntax = "proto3";  // 语法协议
package pack.base;  // 命名空间

// 枚举,登录类型
enum LoginType{
    GUEST = 0;
    APPLE = 1;
    RENREN = 2;
    FACEBOOK = 3;
    KAIXIN = 4;
    GAMECENTER = 5;
    SINA = 6;
    WEICHAT = 7;
    ALIPAY = 8;
}

message LoginReq{
    string sig = 1;
    LoginType login_type = 2;
    string openid = 3;
    string channel = 4;
    string version = 5;
    string device_id = 6;
    string mac_id = 7;
    repeated int32 login_num = 8;
}

注意: proto3 仅仅支持 repeated字段修饰,如果使用required,optional编译会报错。

2.2 在proto文件目录,创建编译文件夹:

# mkdir proto_php

2.3 编译proto文件,生成 php类:

# protoc --php_out=./proto_php  pack.proto

目录结构如下:

├─proto_php
│  ├─GPBMetadata
│  │   -- Pack.php
│  │
│  └─Pack
│      └─Base
│          -- LoginReq.php
│          -- LoginType.php
├─ test_proto.php

三、测试脚本:

# cat test_proto.php
<?php

defined('DS') or define('DS', DIRECTORY_SEPARATOR);
/**
 * protobuf 测试, php
 * 
 * 编译proto文件,生成 php类
 * # protoc --php_out=./proto_php  pack.proto
 */

require __DIR__ . DS . 'proto_php' .DS. 'GPBMetadata' . DS . 'Pack.php';
require __DIR__ . DS . 'proto_php' .DS. 'Pack' . DS . 'Base' . DS .'LoginReq.php';
require __DIR__ . DS . 'proto_php' .DS. 'Pack' . DS . 'Base' . DS . 'LoginType.php';

use Pack\Base\{LoginReq, LoginType};

// 实例化 message类
$login_req = new LoginReq();

$login_req->setSig(md5(microtime(true)));
$login_req->setLoginType(LoginType::APPLE);
$login_req->setOpenid(sprintf('openid_%u', mt_rand(1000, 29520)));
$login_req->setChannel(sprintf('channel_00%u', mt_rand(1000, 29520)));
$login_req->setVersion(sprintf('V.%u.%u', mt_rand(1, 3), mt_rand(4, 20)));
$login_req->setDeviceId(sprintf('A00-B%s-C%s-D%s', mt_rand(1,6), mt_rand(8,70), mt_rand(100,237)));
$login_req->setMacId(str_pad('MAC', 20));
$login_req->setLoginNum(range(1,10));

// 序列化成二进制字符串
$data = $login_req->SerializeToString();

// 解析
$login_rsp = new LoginReq();
// 二进制字符串反序列化
$login_rsp->mergeFromString($data);

echo 'sig: ', $login_rsp->getSig(), PHP_EOL;
echo 'login_type: ', $login_rsp->getLoginType(), PHP_EOL;
echo 'openid: ', $login_rsp->getOpenid(), PHP_EOL;
echo 'channel: ', $login_rsp->getChannel(), PHP_EOL;
echo 'version: ', $login_rsp->getVersion(), PHP_EOL;
echo 'device_id: ', $login_rsp->getDeviceId(), PHP_EOL;

// repeated 类型的处理:
echo 'login_num size: ', count($login_rsp->getLoginNum()), PHP_EOL;

$login_num = [];
foreach ($login_rsp->getLoginNum() as $key => $value) {
    $login_num[] = $value;
}

echo 'login_num: ', implode(',', $login_num), PHP_EOL;

输出结果如下:

# php -f test_proto.php

sig: 9908432d8bddb847a25c45fce00656ab
login_type: 1
openid: openid_23903
channel: channel_0018442
version: V.2.9
device_id: A00-B1-C24-D170
login_num size: 10
login_num: 1,2,3,4,5,6,7,8,9,10