PHP 生成的代码指南

描述协议缓冲区编译器为任何给定的协议定义生成的 PHP 代码。

在阅读本文档之前,您应该阅读proto3 语言指南。请注意,协议缓冲区编译器目前仅支持 PHP 的 proto3 代码生成。

编译器调用

当使用 --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,但它不会创建 buildbuild/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

生成的选项区分大小写。默认情况下,包转换为帕斯卡大小写。

消息

给定一个简单的消息声明

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。如果指定了此选项,则将其添加到所有生成的 message 类之前。

字段

对于消息类型中的每个字段,都有访问器方法来设置和获取字段。因此,给定一个字段 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);

映射字段

协议缓冲区编译器为每个映射字段生成一个 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。如果指定了此选项,则将其添加到所有生成的 enum 类之前。

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 中的字段。