转自:https://imsoul.blog.csdn.net/article/details/104342034?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf
安装Protoc
用于生成PHP代码
下载地址: https://github.com/protocolbuffers/protobuf/releases
选择Window平台下载地址
这里选择最新版
https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-win64.zip
下载后选择一个目录解压, 这里解压到
D:\Program Files\protoc-3.11.4-win64
设置环境变量
查看是否安装成功
编写 protocol buffers 文件, 安装protoc解压目录的include包含一些样例
D:\Program Files\protoc-3.11.4-win64\include
这里简单编写一个User.proto
syntax = "proto3";
package App.Bean;
message User{
int32 uid = 1;
string username = 2;
string nickname = 3;
int32 age = 4;
int32 sex = 5;
}
新建一个项目, E:\wamp64\www\wwwroot\gitroot\prototest
进入目录新建文件夹proto
把User.proto文件放在proto目录中
执行命令生成文件
protoc --php_out=. proto/User.proto
–php_out=.表示编译成PHP代码,放在当前目录(.),也可以指定文件夹。
protoc --php_out=mylib proto/User.proto
protoc还支持编译其他语言:
$ protoc | grep "=OUT_DIR"
--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file.
编译结果:
生成后的文件结构:
├── GPBMetadata
│ └── Proto
│ └── User.php
└── App
│ └── Bean
│ └── User.php
├── proto
│ └── User.proto
测试代码
$data = [
'uid'=>1,
'username'=>'Soul'
];
$userProto = new User($data);
$userProto->setAge(20);
$userProto->setSex(1);
$str = $userProto->serializeToString();
print_r($userProto->serializeToJsonString());
$userProto2= new User();
$userProto2->mergeFromString($str);
proto语法
官方文档: https://developers.google.com/protocol-buffers/docs/overview
也可以参考 Protobuf3语言指南 写得不错
1、proto3
proto 有proto3 和 proto2。proto3 比 proto2 支持更多语言但 更简洁。去掉了一些复杂的语法和特性,更强调约定而弱化语法。如果是首次使用 Protobuf ,建议使用 proto3 。详见参考文献说明。
需要在proto头部申明:
syntax = "proto3";
如果你没有指定这个,编译器会使用proto2。
2、注释
使用 //,示例:
message UserList {
repeated User list = 1; //用户列表
int32 page = 2; //分页
int32 limit = 3; //分页条数
}
其中写在每个属性后面的注释在生产的代码里面有保留。
3、message
message类似于结构体的概念,最终编译为代码在PHP、JAVA里就是一个类,在golang里是结构体。每一个属性都会生成对应的getXXX、setXXX方法。
4、字段规则
repeated表示这个属性重复N次,在相对应的编程语言中通常是一个空的list。PHP里对应数组。
reserved表示标识号保留暂时不用。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。**切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。**最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。
5、支持的数据类型
.proto Type | Notes C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type |
---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float |
float | float | float | float | float32 | Float | float | float |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int |
uint32 | 使用变长编码 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根据需要) | uint |
uint64 | 使用变长编码 | uint64 | long | int/long | uint64 | Bignum | ulong |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 | long | int/long | int64 | Bignum | long |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根据需要) | uint |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 | long | int/long | uint64 | Bignum | ulong |
sfixed32 | 总是4个字节 | int32 | int | int | int32 | Fixnum 或者 Bignum(根据需要) | int |
sfixed64 | 总是8个字节 | int64 | long | int/long | int64 | Bignum | long |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string | String | str/unicode | string | String (UTF-8) | string |
bytes | 可能包含任意顺序的字节数据。 | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString |
6、默认值说明
string类型,默认值是空字符串
bytes类型,默认值是空bytes
bool类型,默认值是false
数字类型,默认值是0
枚举类型,默认值是第一个枚举值,即0
repeated修饰的属性,默认值是空.
7、枚举
使用enum关键字定义枚举,值必须从0开始:
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
8、引用类型
上面的UserList就引用了User类型。大家可以看一下。
9、import
如果一个proto文件引用了另外一个proto文件,那么可以使用import关键字在头部申明:
import "User.proto";
10、Map类型
proto支持map属性类型的定义,语法如下:
map<key_type,value_type> map_field = N;
示例:
map<string, string> ext = 6; //扩展信息
这个map对于PHP来说就是关联数组,对于golang来说就是Map。
10、Any
Any类型允许包装任意的message类型,可以通过pack()和unpack()(方法名在不同的语言中可能不同)方法打包/解包:
import "google/protobuf/any.proto";
message Response {
google.protobuf.Any data = 1;
}
PHP开发的同学可能觉得Any没必要,因为数组里任何类型都可以放,但是对于强类型语言,数组里的值类型必须是一致的,使用Any类型可以解决这个问题。Any相当于把值包装了一层,这样都是Any类型。
11、服务定义
service UserService {
// 方法名 方法参数 返回值
rpc GetUser(Request) returns (Response);
}
这相当于定义了一个类,里面有一个对外的GetUser()方法。这个通常用于定义RPC服务,与gRPC结合使用。
12、从.proto文件生成了什么?
当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
PHP:每一个Message或者Enum生成一个类,另外还会生成GPBMetadata。
C++:编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
Java:编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
Python:Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
go:编译器会位每个消息类型生成了一个.pd.go文件。
Ruby:编译器会为每个消息类型生成了一个.rb文件。
Objective-C:编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个对应的类。
C#:编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。
IDE插件
1、JetBrains PhpStorm 可以在插件里找到Protobuf安装,重启IDE后就支持proto格式语法了。
2、VScode 在扩展里搜索 Protobuf,安装即可。
3、protobuf的 php 扩展类在ide中没有提示,可将https://github.com/protocolbuffers/protobuf/tree/master/php/src 目录下载到本地,将此目录加到ide的include_path中即可。