2024 年日本 PHP 研討會

PHP 變數與 MongoDB 之間的序列化和反序列化

本文檔討論如何在 BSON 和 PHP 值之間轉換複合結構(例如文件、陣列和物件)。

序列化為 BSON

陣列

如果一個陣列是*緊密陣列* — 也就是空陣列或鍵值從 0 開始且無間隙地連續:*BSON 陣列*。

如果陣列不是緊密排列的 — 也就是說,具有關聯式(字串)鍵值、鍵值不是從 0 開始,或者存在間隙:: BSON 物件

頂層(根)文件,總是序列化為 BSON 文件。

範例

這些會序列化為 BSON 陣列

[ 8, 5, 2, 3 ] => [ 8, 5, 2, 3 ]
[ 0 => 4, 1 => 9 ] => [ 4, 9 ]

這些會序列化為 BSON 文件

[ 0 => 1, 2 => 8, 3 => 12 ] => { "0" : 1, "2" : 8, "3" : 12 }
[ "foo" => 42 ] => { "foo" : 42 }
[ 1 => 9, 0 => 10 ] => { "1" : 9, "0" : 10 }

請注意,這五個範例是完整文件的摘錄,並且僅代表文件中的一個值。

物件

如果物件屬於 stdClass 類別,則序列化為 BSON 文件

如果物件是一個支援的類別,且實作了 MongoDB\BSON\Type 介面,則使用該特定類型的 BSON 序列化邏輯。MongoDB\BSON\Type 實例(不包括 MongoDB\BSON\Serializable)只能序列化為文件欄位值。嘗試將此類物件序列化為根文件將會拋出 MongoDB\Driver\Exception\UnexpectedValueException 例外。

如果物件屬於一個未知的類別,但實作了 MongoDB\BSON\Type 介面,則拋出 MongoDB\Driver\Exception\UnexpectedValueException 例外。

如果物件屬於任何其他類別,且未實作任何特殊介面,則序列化為 BSON 文件。僅保留 公開 屬性,並忽略 保護私有 屬性。

如果物件屬於實作了 MongoDB\BSON\Serializable 介面的類別,則呼叫 MongoDB\BSON\Serializable::bsonSerialize() 並使用返回的陣列或 stdClass 序列化為 BSON 文件或陣列。BSON 類型將由以下決定:

  1. 根文件必須序列化為 BSON 文件。

  2. MongoDB\BSON\Persistable 物件必須序列化為 BSON 文件。

  3. 如果 MongoDB\BSON\Serializable::bsonSerialize() 返回一個緊密排列的陣列,則序列化為 BSON 陣列。

  4. 如果 MongoDB\BSON\Serializable::bsonSerialize() 返回一個非緊密排列的陣列或 stdClass,則序列化為 BSON 文件。

  5. 如果 MongoDB\BSON\Serializable::bsonSerialize() 未返回陣列或 stdClass,則拋出 MongoDB\Driver\Exception\UnexpectedValueException 例外。

如果物件屬於實作了 MongoDB\BSON\Persistable 介面(這意味著也實作了 MongoDB\BSON\Serializable)的類別,則以與前述段落類似的方式取得屬性,但同時還要新增一個額外的屬性 __pclass 作為二進位值,其子類型為 0x80,且資料包含正在被序列化的物件的完整類別名稱。

加到 MongoDB\BSON\Serializable::bsonSerialize() 所返回的陣列或物件的 __pclass 屬性,表示它會覆寫 MongoDB\BSON\Serializable::bsonSerialize() 返回值中的任何 __pclass 鍵/屬性。如果您想避免這種行為並設定自己的 __pclass 值,則不得實作 MongoDB\BSON\Persistable 介面,而應該直接實作 MongoDB\BSON\Serializable 介面。

範例

<?php

class stdClass
{
public
$foo = 42;
}
// => {"foo": 42}

class MyClass
{
public
$foo = 42;
protected
$prot = 'wine';
private
$fpr = 'cheese';
}
// => {"foo": 42}

class AnotherClass1 implements MongoDB\BSON\Serializable
{
public
$foo = 42;
protected
$prot = 'wine';
private
$fpr = 'cheese';

public function
bsonSerialize(): array
{
return [
'foo' => $this->foo, 'prot' => $this->prot];
}
}
// => {"foo": 42, "prot": "wine"}

class AnotherClass2 implements MongoDB\BSON\Serializable
{
public
$foo = 42;

public function
bsonSerialize(): self
{
return
$this;
}
}
// => MongoDB\Driver\Exception\UnexpectedValueException("bsonSerialize() did not return an array or stdClass")

class AnotherClass3 implements MongoDB\BSON\Serializable
{
private
$elements = ['foo', 'bar'];

public function
bsonSerialize(): array
{
return
$this->elements;
}
}
// => {"0": "foo", "1": "bar"}

/**
* Nesting Serializable classes
*/

class AnotherClass4 implements MongoDB\BSON\Serializable
{
private
$elements = [0 => 'foo', 2 => 'bar'];

public function
bsonSerialize(): array
{
return
$this->elements;
}
}
// => {"0": "foo", "2": "bar"}

class ContainerClass1 implements MongoDB\BSON\Serializable
{
public
$things;

public function
__construct()
{
$this->things = new AnotherClass4();
}

function
bsonSerialize(): array
{
return [
'things' => $this->things];
}
}
// => {"things": {"0": "foo", "2": "bar"}}


class AnotherClass5 implements MongoDB\BSON\Serializable
{
private
$elements = [0 => 'foo', 2 => 'bar'];

public function
bsonSerialize(): array
{
return
array_values($this->elements);
}
}
// => {"0": "foo", "1": "bar"} as a root class
["foo", "bar"] as a nested value

class ContainerClass2 implements MongoDB\BSON\Serializable
{
public
$things;

public function
__construct()
{
$this->things = new AnotherClass5();
}

public function
bsonSerialize(): array
{
return [
'things' => $this->things];
}
}
// => {"things": ["foo", "bar"]}


class AnotherClass6 implements MongoDB\BSON\Serializable
{
private
$elements = ['foo', 'bar'];

function
bsonSerialize(): object
{
return (object)
$this->elements;
}
}
// => {"0": "foo", "1": "bar"}

class ContainerClass3 implements MongoDB\BSON\Serializable
{
public
$things;

public function
__construct()
{
$this->things = new AnotherClass6();
}

public function
bsonSerialize(): array
{
return [
'things' => $this->things];
}
}
// => {"things": {"0": "foo", "1": "bar"}}

class UpperClass implements MongoDB\BSON\Persistable
{
public
$foo = 42;
protected
$prot = 'wine';
private
$fpr = 'cheese';

private
$data;

public function
bsonUnserialize(array $data): void
{
$this->data = $data;
}

public function
bsonSerialize(): array
{
return [
'foo' => $this->foo, 'prot' => $this->prot];
}
}
// => {"foo": 42, "prot": "wine", "__pclass": {"$type": "80", "$binary": "VXBwZXJDbGFzcw=="}}

?>

從 BSON 反序列化

警告

BSON 文件技術上可以包含重複的鍵,因為文件是以鍵值對列表的形式儲存的;然而,應用程式應避免產生具有重複鍵的文件,因為伺服器和驅動程式的行為可能未定義。由於 PHP 物件和陣列不能有重複的鍵,因此在解碼具有重複鍵的 BSON 文件時,資料也可能會遺失。

舊版 mongo 擴充功能將 BSON 文件和陣列都反序列化為 PHP 陣列。雖然 PHP 陣列使用起來很方便,但這種行為是有問題的,因為不同的 BSON 類型可能會反序列化為相同的 PHP 值(例如 {"0": "foo"}["foo"]),並且無法推斷原始的 BSON 類型。預設情況下,mongodb 擴充功能透過確保 BSON 陣列和文件分別轉換為 PHP 陣列和物件來解決這個問題。

對於複合類型,有三種資料類型

指頂層 BSON 文件

文件

指嵌入的 BSON 文件

陣列

指 BSON 陣列

除了這三種集合類型之外,還可以設定文件中特定的欄位來對應到下面提到的資料類型。例如,以下類型映射允許您將 "addresses" 陣列中的每個嵌入文件映射到 Address 類別,並且將這些嵌入地址文件中的每個 "city" 欄位映射到 City 類別

[
    'fieldPaths' => [
        'addresses.$' => 'MyProject\Address',
        'addresses.$.city' => 'MyProject\City',
    ],
]

這三種資料類型中的每一種,以及特定欄位的映射,都可以映射到不同的 PHP 類型。可能的映射值如下:

未設定NULL(預設)

  • BSON 陣列將被反序列化為 PHP 陣列

  • 沒有 __pclass 屬性 [1] 的 BSON 文件(根或嵌入)會變成 PHP stdClass 物件,每個 BSON 文件鍵都設定為公開的 stdClass 屬性。

  • 具有 __pclass 屬性 [1] 的 BSON 文件(根或嵌入)會變成由 __pclass 屬性定義的類別名稱的 PHP 物件。

    如果指定的類別實作了 MongoDB\BSON\Persistable 介面,則 BSON 文件的屬性,包括 __pclass 屬性,會作為關聯陣列傳送到 MongoDB\BSON\Unserializable::bsonUnserialize() 函式以初始化物件的屬性。

    如果指定的類別不存在或沒有實作 MongoDB\BSON\Persistable 介面,則會使用 stdClass,並且每個 BSON 文件鍵值(包括 __pclass)都會被設定為公開的 stdClass 屬性。

    __pclass 功能依賴於該屬性作為擷取的 MongoDB 文件的一部分。如果您在查詢文件時使用投影,則需要在投影中包含 __pclass 欄位才能使此功能正常運作。

「array」

將 BSON 陣列或 BSON 文件轉換為 PHP 陣列。不會對 __pclass 屬性進行特殊處理[1],但如果它存在於 BSON 文件中,則可能會被設定為返回陣列中的一個元素。

"object""stdClass"

將 BSON 陣列或 BSON 文件轉換為 stdClass 物件。不會對 __pclass 屬性進行特殊處理[1],但如果它存在於 BSON 文件中,則可能會被設定為返回物件中的一個公開屬性。

「bson」

將 BSON 陣列轉換為 MongoDB\BSON\PackedArray,將 BSON 文件轉換為 MongoDB\BSON\Document,無論 BSON 文件是否具有 __pclass 屬性[1]

注意bson 值僅適用於三種根類型,不適用於欄位特定映射。

任何其他字串

定義 BSON 陣列或 BSON 物件應反序列化為的類別名稱。對於包含 __pclass 屬性的 BSON 物件,該類別將具有優先權。

如果指定的類別不存在、不是具體類別(即它是抽象類別或介面),或者沒有實作 MongoDB\BSON\Unserializable,則會擲出 MongoDB\Driver\Exception\InvalidArgumentException 例外。

如果 BSON 物件具有 __pclass 屬性,並且該類別存在並實作了 MongoDB\BSON\Persistable,它將取代類型映射中提供的類別。

BSON 文件的屬性,*包括* __pclass 屬性(如果存在),將作為關聯陣列傳送到 MongoDB\BSON\Unserializable::bsonUnserialize() 函式以初始化物件的屬性。

類型映射

可以透過 MongoDB\Driver\Cursor 物件上的 MongoDB\Driver\Cursor::setTypeMap() 方法,或 MongoDB\BSON\toPHP()MongoDB\BSON\Document::toPHP()MongoDB\BSON\PackedArray::toPHP()$typeMap 參數來設定類型映射。除了欄位特定類型之外,還可以單獨設定三個類別(*根*、*文件* 和 *陣列*)中的每一個。

如果映射中的值是 NULL,則表示與該項的*預設*值相同。

範例

這些範例使用以下類別

MyClass

未實作任何介面的

YourClass

實作了 MongoDB\BSON\Unserializable 介面

OurClass

實作了 MongoDB\BSON\Persistable 介面

TheirClass

繼承自 OurClass

YourClass、OurClass 和 TheirClass 的 MongoDB\BSON\Unserializable::bsonUnserialize() 方法會迭代陣列並設定屬性,不做任何修改。它*還會*將 $unserialized 屬性設為 true

<?php

function bsonUnserialize( array $map )
{
foreach (
$map as $k => $value )
{
$this->$k = $value;
}
$this->unserialized = true;
}

/* typemap: [] (all defaults) */
{ "foo": "yes", "bar" : false }
  -> stdClass { $foo => 'yes', $bar => false }

{ "foo": "no", "array" : [ 5, 6 ] }
  -> stdClass { $foo => 'no', $array => [ 5, 6 ] }

{ "foo": "no", "obj" : { "embedded" : 3.14 } }
  -> stdClass { $foo => 'no', $obj => stdClass { $embedded => 3.14 } }

{ "foo": "yes", "__pclass": "MyClass" }
  -> stdClass { $foo => 'yes', $__pclass => 'MyClass' }

{ "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "MyClass" } }
  -> stdClass { $foo => 'yes', $__pclass => Binary(0x80, 'MyClass') }

{ "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "YourClass") }
  -> stdClass { $foo => 'yes', $__pclass => Binary(0x80, 'YourClass') }

{ "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "OurClass") }
  -> OurClass { $foo => 'yes', $__pclass => Binary(0x80, 'OurClass'), $unserialized => true }

{ "foo": "yes", "__pclass": { "$type" : "44", "$binary" : "YourClass") }
  -> stdClass { $foo => 'yes', $__pclass => Binary(0x44, 'YourClass') }

/* typemap: [ "root" => "MissingClass" ] */
{ "foo": "yes" }
  -> MongoDB\Driver\Exception\InvalidArgumentException("MissingClass does not exist")

/* typemap: [ "root" => "MyClass" ] */
{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } }
  -> MongoDB\Driver\Exception\InvalidArgumentException("MyClass does not implement Unserializable interface")

/* typemap: [ "root" => "MongoDB\BSON\Unserializable" ] */
{ "foo": "yes" }
  -> MongoDB\Driver\Exception\InvalidArgumentException("Unserializable is not a concrete class")

/* typemap: [ "root" => "YourClass" ] */
{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MongoDB\BSON\Unserializable" } }
  -> YourClass { $foo => "yes", $__pclass => Binary(0x80, "MongoDB\BSON\Unserializable"), $unserialized => true }

/* typemap: [ "root" => "YourClass" ] */
{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } }
  -> YourClass { $foo => "yes", $__pclass => Binary(0x80, "MyClass"), $unserialized => true }

/* typemap: [ "root" => "YourClass" ] */
{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "OurClass" } }
  -> OurClass { $foo => "yes", $__pclass => Binary(0x80, "OurClass"), $unserialized => true }

/* typemap: [ "root" => "YourClass" ] */
{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "TheirClass" } }
  -> TheirClass { $foo => "yes", $__pclass => Binary(0x80, "TheirClass"), $unserialized => true }

/* typemap: [ "root" => "OurClass" ] */
{ foo: "yes", "__pclass" : { "$type": "80", "$binary": "TheirClass" } }
  -> TheirClass { $foo => "yes", $__pclass => Binary(0x80, "TheirClass"), $unserialized => true }

/* typemap: [ 'root' => 'YourClass' ] */
{ foo: "yes", "__pclass" : { "$type": "80", "$binary": "YourClass" } }
  -> YourClass { $foo => 'yes', $__pclass => Binary(0x80, 'YourClass'), $unserialized => true }

/* typemap: [ 'root' => 'array', 'document' => 'array' ] */
{ "foo": "yes", "bar" : false }
  -> [ "foo" => "yes", "bar" => false ]

{ "foo": "no", "array" : [ 5, 6 ] }
  -> [ "foo" => "no", "array" => [ 5, 6 ] ]

{ "foo": "no", "obj" : { "embedded" : 3.14 } }
  -> [ "foo" => "no", "obj" => [ "embedded => 3.14 ] ]

{ "foo": "yes", "__pclass": "MyClass" }
  -> [ "foo" => "yes", "__pclass" => "MyClass" ]

{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } }
  -> [ "foo" => "yes", "__pclass" => Binary(0x80, "MyClass") ]

{ "foo": "yes", "__pclass" : { "$type": "80", "$binary": "OurClass" } }
  -> [ "foo" => "yes", "__pclass" => Binary(0x80, "OurClass") ]

/* typemap: [ 'root' => 'object', 'document' => 'object' ] */
{ "foo": "yes", "__pclass": { "$type": "80", "$binary": "MyClass" } }
  -> stdClass { $foo => "yes", "__pclass" => Binary(0x80, "MyClass") }

新增註釋

使用者貢獻的註釋

此頁面沒有使用者貢獻的註釋。
To Top