PHP 生成代码指南
在阅读本文档之前,您应该阅读proto3 语言指南。请注意,协议缓冲区编译器目前仅支持 proto3 代码生成用于 PHP。
编译器调用
通过使用 --php_out=
命令行标志调用协议缓冲区编译器,可以生成 PHP 输出。--php_out=
选项的参数是您希望编译器写入 PHP 输出的目录。为了符合 PSR-4 规范,编译器会创建一个与 proto 文件中定义的包对应的子目录。此外,对于 proto 文件输入的每个消息,编译器会在包的子目录中创建一个单独的文件。消息的输出文件名由三部分组成
- 基本目录:proto 路径(使用
--proto_path=
或-I
命令行标志指定)被输出路径(使用--php_out=
标志指定)替换。 - 子目录:包名中的
.
会被操作系统目录分隔符替换。每个包名组件的首字母都会大写。 - 文件:消息名后附加
.php
。
因此,例如,假设您按如下方式调用编译器
protoc --proto_path=src --php_out=build/gen src/example.proto
并且 src/example.proto
定义如下
package foo.bar;
message MyMessage {}
编译器将读取文件 src/foo.proto
并生成输出文件:build/gen/Foo/Bar/MyMessage.php
。编译器会根据需要自动创建目录 build/gen/Foo/Bar
,但它将不会创建 build
或 build/gen
;它们必须已经存在。
包
默认情况下,.proto
文件中定义的包名用于为生成的 PHP 类生成模块结构。给定一个文件,例如
package foo.bar;
message MyMessage {}
协议编译器生成一个名为 Foo\Bar\MyMessage
的输出类。
命名空间选项
编译器支持额外的选项来定义 PHP 和元数据命名空间。定义后,这些选项将用于生成模块结构和命名空间。给定如下选项
package foo.bar;
option php_namespace = "baz\\qux";
option php_metadata_namespace = "Foo";
message MyMessage {}
协议编译器生成一个名为 baz\qux\MyMessage
的输出类。该类将具有命名空间 namespace baz\qux
。
协议编译器生成一个名为 Foo\Metadata
的元数据类。该类将具有命名空间 namespace Foo
。
生成的选项区分大小写。默认情况下,包会被转换为 Pascal 大小写。
消息
给定一个简单的消息声明
message Foo {}
协议缓冲区编译器生成一个名为 Foo
的 PHP 类。该类继承自一个公共基类 Google\Protobuf\Internal\Message
,该基类提供了用于编码和解码消息类型的方法,示例如下
$from = new Foo();
$from->setInt32(1);
$from->setString('a');
$from->getRepeatedInt32()[] = 1;
$from->getMapInt32Int32()[1] = 1;
$data = $from->serializeToString();
try {
$to->mergeFromString($data);
} catch (Exception $e) {
// Handle parsing error from invalid data.
...
}
您不应创建自己的 Foo
子类。生成的类并非设计用于子类化,可能导致“脆弱基类”问题。
嵌套消息会导致生成一个同名的 PHP 类,其名称以包含消息为前缀并用下划线分隔,因为 PHP 不支持嵌套类。因此,例如,如果在 .proto
文件中有如下定义
message TestMessage {
optional int32 a = 1;
message NestedMessage {...}
}
编译器将生成以下类
class TestMessage {
public a;
}
// PHP doesn’t support nested classes.
class TestMessage_NestedMessage {...}
如果消息类名是保留字(例如,Empty
),则会在类名之前加上前缀 PB
class PBEmpty {...}
我们还提供了文件级别的选项 php_class_prefix
。如果指定了此选项,它将作为前缀添加到所有生成的消息类名称之前。
字段
对于消息类型中的每个字段,都有用于设置和获取字段的访问器方法。因此,给定字段 x
,您可以编写
$m = new MyMessage();
$m->setX(1);
$val = $m->getX();
$a = 1;
$m->setX($a);
每当您设置字段时,都会根据该字段的声明类型进行类型检查。如果值类型错误(或超出范围),将引发异常。默认情况下,允许在整数、浮点数和数字字符串之间进行类型转换(例如,将值分配给字段或向重复字段添加元素)。不允许的转换包括所有与数组或对象的相互转换。浮点数到整数溢出的转换是未定义的。
您可以在标量值类型表中查看每种标量协议缓冲区类型对应的 PHP 类型。
单一消息字段
消息类型的字段默认为 nil,并且在访问时不会自动创建。因此,您需要显式创建子消息,如下所示
$m = new MyMessage();
$m->setZ(new SubMessage());
$m->getZ()->setFoo(42);
$m2 = new MyMessage();
$m2->getZ()->setFoo(42); // FAILS with an exception
您可以将任何实例分配给消息字段,即使该实例也被其他地方持有(例如,作为另一个消息上的字段值)。
重复字段
协议缓冲区编译器为每个重复字段生成一个特殊的 RepeatedField
类。因此,例如,给定以下字段
repeated int32 foo = 1;
生成的代码允许您执行以下操作
$m->getFoo()[] =1;
$m->setFoo($array);
Map 字段
协议缓冲区编译器为每个 map 字段生成一个 MapField
类。因此,给定此字段
map<int32, int32> weight = 1;
您可以使用生成的代码执行以下操作
$m->getWeight()[1] = 1;
枚举
PHP 没有原生枚举,因此协议缓冲区编译器会为 .proto
文件中的每个枚举类型生成一个 PHP 类,就像生成消息类一样,并为每个值定义常量。因此,给定此枚举
enum TestEnum {
Default = 0;
A = 1;
}
编译器生成以下类
class TestEnum {
const DEFAULT = 0;
const A = 1;
}
与消息类似,嵌套枚举也将生成一个同名的 PHP 类,其名称以包含消息为前缀并用下划线分隔,因为 PHP 不支持嵌套类。
class TestMessage_NestedEnum {...}
如果枚举类或值名是保留字(例如,Empty
),则会在类或值名之前加上前缀 PB
class PBEmpty {
const PBECHO = 0;
}
我们还提供了文件级别的选项 php_class_prefix
。如果指定了此选项,它将作为前缀添加到所有生成的枚举类名称之前。
Oneof
对于 oneof,协议缓冲区编译器生成的代码与处理常规单一字段的代码类似,但还会添加一个特殊的访问器方法,该方法允许您找出哪个 oneof 字段(如果存在)已被设置。因此,给定此消息
message TestMessage {
oneof test_oneof {
int32 oneof_int32 = 1;
int64 oneof_int64 = 2;
}
}
编译器生成以下字段和特殊方法
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(); // Return field name
}
该访问器方法的名称基于 oneof 的名称,并返回一个枚举值,表示 oneof 中当前已设置的字段。