PHP 產生程式碼指南

描述 protocol buffer 編譯器為任何給定的協定定義產生的 PHP 程式碼。

在閱讀本文檔之前,您應該先閱讀proto3 語言指南。請注意,protocol buffer 編譯器目前僅支援 PHP 的 proto3 程式碼產生。

編譯器調用

當使用 --php_out= 命令列旗標調用時,protocol buffer 編譯器會產生 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

產生的選項區分大小寫。預設情況下,套件會轉換為 Pascal 命名法。

訊息

給定一個簡單的訊息宣告

message Foo {}

protocol buffer 編譯器會產生一個名為 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);

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

您可以在純量值類型表中查看每個純量 protocol buffers 類型的對應 PHP 類型。

單數訊息欄位

具有訊息類型的欄位預設為 nil,並且在存取欄位時不會自動建立。因此,您需要明確建立子訊息,如下所示

$m = new MyMessage();
$m->setZ(new SubMessage());
$m->getZ()->setFoo(42);

$m2 = new MyMessage();
$m2->getZ()->setFoo(42);  // FAILS with an exception

您可以將任何執行個體指派給訊息欄位,即使該執行個體也保留在其他地方(例如,作為另一個訊息上的欄位值)。

重複欄位

protocol buffer 編譯器為每個重複欄位產生一個特殊的 RepeatedField。所以,舉例來說,給定以下欄位

repeated int32 foo = 1;

產生的程式碼讓您可以執行此操作

$m->getFoo()[] =1;
$m->setFoo($array);

地圖欄位

protocol buffer 編譯器為每個地圖欄位產生一個 MapField。所以給定這個欄位

map<int32, int32> weight = 1;

您可以使用產生的程式碼執行以下操作

$m->getWeight()[1] = 1;

列舉

PHP 沒有原生列舉,因此 protocol buffer 編譯器會為您的 .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,protocol buffer 編譯器產生的程式碼與常規單數欄位相同,但也新增了一個特殊的存取器方法,讓您可以找出設定了哪個 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 中的欄位。