PHP 生成程式碼指南

描述協議緩衝區編譯器為任何給定協議定義生成的 PHP 程式碼。

在閱讀本文件之前,您應該閱讀proto3 語言指南Editions 語言指南。請注意,協議緩衝區編譯器目前僅支援 proto3 和 Editions 為 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 定義如下

edition = "2023";
package foo.bar;
message MyMessage {}

編譯器將讀取檔案 src/foo.proto 並生成輸出檔案:build/gen/Foo/Bar/MyMessage.php。編譯器將根據需要自動建立目錄 build/gen/Foo/Bar,但它不會建立 buildbuild/gen;它們必須已經存在。

包(Packages)

.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 {
  int32 int32_value = 1;
  string string_value = 2;
  repeated int32 repeated_int32_value = 3;
  map<int32, int32> map_int32_int32_value = 4;
}

協議緩衝區編譯器生成一個名為 Foo 的 PHP 類。該類繼承自一個公共基類 Google\Protobuf\Internal\Message,它提供了用於編碼和解碼訊息型別的方法,如以下示例所示

$from = new Foo();
$from->setInt32Value(1);
$from->setStringValue('a');
$from->getRepeatedInt32Value()[] = 1;
$from->getMapInt32Int32Value()[1] = 1;
$data = $from->serializeToString();
$to = new Foo();
try {
  $to->mergeFromString($data);
} catch (Exception $e) {
  // Handle parsing error from invalid data.
  ...
}

您不應該建立自己的 Foo 子類。生成的類並非設計用於子類化,並可能導致“脆弱的基類”問題。

巢狀訊息會導致生成一個同名的 PHP 類,並以其包含訊息作為字首,用下劃線分隔,因為 PHP 不支援巢狀類。因此,例如,如果您的 .proto 檔案中有以下內容

message TestMessage {
  message NestedMessage {
    int32 a = 1;
  }
}

編譯器將生成以下類

// PHP doesn’t support nested classes.
class TestMessage_NestedMessage {
  public function __construct($data = NULL) {...}
  public function getA() {...}
  public function setA($var) {...}
}

如果訊息類名是保留字(例如,Empty),則在類名前新增字首 PB

class PBEmpty {...}

我們還提供了檔案級別的選項 php_class_prefix。如果指定了此選項,它將作為字首新增到所有生成的訊息類中。

欄位

對於訊息型別中的每個欄位,協議緩衝區編譯器都會生成一組訪問器方法來設定和獲取該欄位。訪問器方法使用從 snake_case 欄位名轉換為 PascalCase 的名稱。因此,給定欄位 field_name,訪問器方法將是 getFieldNamesetFieldName

// optional MyEnum optional_enum
$m->getOptionalEnum();
$m->setOptionalEnum(MyEnum->FOO);
$m->hasOptionalEnum();
$m->clearOptionalEnum();

// MyEnum implicit_enum
$m->getImplicitEnum();
$m->setImplicitEnum(MyEnum->FOO);

每當您設定一個欄位時,值都會根據該欄位的宣告型別進行型別檢查。如果值型別錯誤(或超出範圍),將引發異常。預設情況下,允許在整數、浮點數和數字字串之間進行型別轉換(例如,將值分配給欄位或向重複欄位新增元素)。不允許的轉換包括所有到/從陣列或物件的轉換。浮點數到整數的溢位轉換是未定義的。

您可以在標量值型別表中檢視每個標量協議緩衝區型別對應的 PHP 型別。

has...clear...

對於具有顯式存在的欄位,編譯器會生成一個 has...() 方法。如果欄位已設定,此方法返回 true

編譯器還會生成一個 clear...() 方法。此方法取消設定欄位。呼叫此方法後,has...() 將返回 false

對於具有隱式存在的欄位,編譯器不會生成 has...()clear...() 方法。對於這些欄位,您可以透過將欄位值與預設值進行比較來檢查其是否存在。

奇異訊息欄位

對於具有訊息型別的欄位,編譯器會生成與標量型別相同的訪問器方法。

具有訊息型別的欄位預設為 null,並且在訪問時不會自動建立。因此,您需要顯式建立子訊息,如下所示

$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。如果指定了此選項,它將作為字首新增到所有生成的列舉類中。

Oneof

對於 oneof,協議緩衝區編譯器會為 oneof 中的每個欄位生成一個 hasclear 方法,以及一個特殊的訪問器方法,讓您找出哪個 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 中當前已設定的欄位。如果 oneof 未設定,該方法返回一個空字串。

當您設定 oneof 中的一個欄位時,它會自動清除 oneof 中的所有其他欄位。如果您想在 oneof 中設定多個欄位,則必須在單獨的語句中進行。

$m = new TestMessage();
$m->setOneofInt32(42); // $m->hasOneofInt32() is true
$m->setOneofInt64(123); // $m->hasOneofInt32() is now false