本文檔討論如何在 BSON 和 PHP 值之間轉換複合結構(例如文件、陣列和物件)。
如果一個陣列是*緊密陣列* — 也就是空陣列或鍵值從 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 類型將由以下決定:
根文件必須序列化為 BSON 文件。
MongoDB\BSON\Persistable 物件必須序列化為 BSON 文件。
如果 MongoDB\BSON\Serializable::bsonSerialize() 返回一個緊密排列的陣列,則序列化為 BSON 陣列。
如果 MongoDB\BSON\Serializable::bsonSerialize() 返回一個非緊密排列的陣列或 stdClass,則序列化為 BSON 文件。
如果 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 文件技術上可以包含重複的鍵,因為文件是以鍵值對列表的形式儲存的;然而,應用程式應避免產生具有重複鍵的文件,因為伺服器和驅動程式的行為可能未定義。由於 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 類型。可能的映射值如下:
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,則表示與該項的*預設*值相同。
這些範例使用以下類別
未實作任何介面的
實作了 MongoDB\BSON\Unserializable 介面
實作了 MongoDB\BSON\Persistable 介面
繼承自 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") }