本文介绍PB协议与PHP语言相关的特性和知识点,以及如何使用PHP解析pb协议。
· packages
proto文件指定了package后,在编译为PHP文件时会根据package包名路径为构建出的PHP文件设置命名空间,并且创建对应命名空间的目录。
package foo.bar;
message MyMessage {}
protoc编译器会为上述proto文件生成的PHP文件设置命名空间为 “Foo\Bar\MyMessage”。
· Messages
message Person {
string name = 1;
int32 age = 2;
string sex = 3;
}
如上所示,PB编译器生成PHP代码后,PHP代码里会包含一个Foo的类。Foo类继承了PB提供的Message基类,Message基类提供了一系列的访问器和修改器供我们访问和修改message中的字段。
$person = new Test\Person();
$person->setName("lailaiji");
$person->setAge("28");
$person->setSex(true);
$data = $person->serializeToString();
file_put_contents('data.bin',$data);
又例如:
$m = new MyMessage();
$m->setX(1);
$val = $m->getX();
$a = 1;
$m->setX($a);
Message对象还提供了序列化和反序列化的方法。
serializeToString:序列化成二进制字符串
serializeToJsonString:序列化成JSON字符串
mergeFromString:二进制字符串反序列化
mergeFromJsonString:Json字符串反序列化
序列化示例
<?php
include 'vendor/autoload.php';
include 'GPBMetadata/Person.php';
include 'Test/Person.php';
$person = new Test\Person();
$person->setName("lailaiji");
$person->setAge("28");
$person->setSex(true);
$data = $person->serializeToString();
file_put_contents('data.bin',$data);
反序列化示例
<?php
include 'vendor/autoload.php';
include 'GPBMetadata/Person.php';
include 'Test/Person.php';
$bindata = file_get_contents('./data.bin');
$person = new Test\Person();
$person->mergeFromString($bindata);
echo $person->getName();
获取一个已有内容的message的所有字段值
$json = $message->serializeToJsonString();
$data = json_decode($json, true);
注意:
1、当你使用修改器(set方法)修改message的一个字段时,PHP 会根据该字段的声明类型对该值进行类型检查。如果值的类型错误(或超出范围),则会引发异常。默认情况下,允许在整数、浮点数和数字字符串之间进行类型转换。不允许的转换包括与数组或对象的所有转换。
2、消息类型的字段(即字段的类型是一个嵌套message)的默认值是null,且访问该字段时不会自动创建。因此,您需要显式创建子消息。
例如:
$m = new MyMessage();
$m->setZ(new SubMessage());
$m->getZ()->setFoo(42);
$m2 = new MyMessage();
$m2->getZ()->setFoo(42); // 报错,原因是getZ()返回null
3、repeated字段和map字段的操作类似数组
proto文件内容:
repeated int32 foo = 1;
map<int32, int32> weight = 1;
生成的PHP代码允许您执行此操作:
$m->getFoo()[] =1;
$m->setFoo($array);
$m->getWeight()[1] = 1;
需要注意,PHP中使用message对象的访问器获取一个repeated对象时,返回的是一个RepeatedField对象,而非数组。如果需要得到数组,可以使用iterator_to_array()函数。
$foo = iterator_to_array($m->getFoo();
4、PHP没有原生枚举,因此PB编译器会为.proto文件中的每个枚举类型生成一个 PHP 类。
enum TestEnum {
Default = 0;
A = 1;
}
编译后:
class TestEnum {
const DEFAULT = 0;
const A = 1;
}
5、对于oneof,PB 编译器生成与常规单数字段相同的代码,但还添加了一个特殊的访问器方法,可让您找出设置了哪个 oneof 字段(如果有)。此时如果要用访问器或修改器方法,就不再是get某个具体字段。
message TestMessage {
oneof test_oneof {
int32 oneof_int32 = 1;
int64 oneof_int64 = 2;
}
}
编译器编译成的PHP文件会生成以下字段和特殊方法:
class TestMessage {
private oneof_int32;
private oneof_int64;
public function getOneofInt32();
public function setOneofInt32($var);
public function getOneofInt64();
public function setOneofInt64($var);
public function getTestOneof(); // 返回字段名
}
访问器方法的名称基于 oneof 的名称,并返回一个枚举值,该值表示当前设置的 oneof 中的字段值。
6、对any类型的字段设置值时,不能够直接设置而是要使用setValue()方法。
message TestProductAttr{
map<string, google.protobuf.Any> attr = 1;
}
正确做法:
$attrs = ["color"=>"read", "size"=>16];
$attrPb = new \protobuf\Test\TestProductAttr();
foreach($attr as $k=>$v){
$any = new \Google\Protobuf\Any();
$any->setTypeUrl('type.googleapis.com/google.protobuf.StringValue');
$any->setValue($v);
$attr[$k] = $any;
}
$attrPb->setAttr($attrs);
需要注意,any类型是一种泛型,因此设置的时候必须指定any实际的类型,否则在反编码的时候程序就不知道应该按整型、字符串还是map或数组等类型对其进行反编码。因此 protobuf 提供了type url属性供用户指明一个any类型的值应该按protobuf规定的哪种类型进行编码和反编码。
type url的格式一般为 type.googleapis.com/包名.message名,例如type.googleapis.com/google.protobuf.StringValue 就表示要按照protobuf官方内置的 StringValue这种内置的message类型对any类型进行解析,也就是解析为字符串格式。
protobuf内置的message类型可以在 google/protobuf 目录中查看,该目录是PHP使用composer安装 google/protobuf 依赖时生成的。
张柏沛IT技术博客 > PB协议(二)Protobuf的PHP开发教程