## Сериализация и десериализация PHP-переменных в модуле MongoDB

В документе обсуждается, как составные структуры наподобие документов, массивов и объектов преобразовываются между значениями формата BSON и PHP-значениями.

## Сериализация в BSON

## Массивы

Если массив представляет собой упакованный массив — то есть пустой массив или ключи начинаются с 0 и идут последовательно без пробелов: BSON-массив.

Если массив не упакован — то есть содержит ассоциативные (строковые) ключи, ключи не начинаются с 0 или содержат пробелы: BSON-объект

Документ верхнего уровня (корневой) всегда сериализуется как BSON-документ.

## Примеры

Эти PHP-массивы сериализуются как BSON-массив:

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

Эти PHP-массивы сериализуются как 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-документ. Остаются только открытые (public) свойства, а защищённые (protected) и закрытые (private) свойства игнорируются.

Если объект принадлежит к классу, который реализует интерфейс MongoDB\BSON\Serializable, вызывают метод MongoDB\BSON\Serializable::bsonSerialize() и сериализуют массив или stdClass, которые возвращает метод, как BSON-документ или как 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 в виде Binary-значения с подтипом `0x80` и данными, которые содержат полное имя класса объекта, который сериализуется.

Свойство __pclass добавляется в массив или объект, который возвращает метод MongoDB\BSON\Serializable::bsonSerialize(), что означает, что оно перезапишет любой ключ или свойство __pclass в значении, которое возвращает метод MongoDB\BSON\Serializable::bsonSerialize(). Если требуется избежать такого поведения и установить собственное значение __pclass, вместо реализации интерфейса MongoDB\BSON\Persistable следует напрямую реализовать интерфейс MongoDB\BSON\Serializable.

## Примеры

    <?php

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

    class MyClass
    {
        public $foo = 42;
        protected $prot = 'вино';
        private $fpr = 'сыр';
    } // => {"foo": 42}

    class AnotherClass1 implements MongoDB\BSON\Serializable
    {
        public $foo = 42;
        protected $prot = 'вино';
        private $fpr = 'сыр';

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

    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"}

    /**
     * Вложенные сериализуемые классы
     */

    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 = 'вино';
        private $fpr = 'сыр';

        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": "вино", "__pclass": {"$type": "80", "$binary": "VXBwZXJDbGFzcw=="}}

    ?>

## Десериализация из BSON

Внимание

Документы BSON технически могут содержать повторяющиеся ключи, поскольку документы хранятся в виде списка пар ключ-значение; однако приложениям следует воздерживаться от создания документов с дубликатами ключей, поскольку поведение сервера и драйвера может быть неопределённым. Поскольку объекты и массивы PHP не могут иметь повторяющихся ключей, данные также могут быть потеряны при декодировании документа BSON с повторяющимися ключами.

Устаревший модуль `mongo` десериализовывал BSON-документы и BSON-массивы в PHP-массивы. Хотя с PHP-массивами удобно работать, такое поведение было проблематичным, поскольку разные BSON-типы могли десериализоваться до одного и того же PHP-значения (например, `{"0": "foo"}` и `["foo"]`), что делало невозможным определение оригинального BSON-типа. По умолчанию модуль `mongodb` решает эту проблему и гарантирует, что BSON-массивы преобразуются в PHP-массивы, а BSON-документы в PHP-объекты.

Для составных типов существует три типа данных:

root
  относится только к BSON-документу верхнего уровня
document
  относится только к встроенным BSON-документам
array
  относится к BSON-массивам

Помимо трёх групповых типов, также можно настроить определённые поля в документе для сопоставления с типами данных, как указывает следующий фрагмент кода. В качестве примера, следующая карта типов позволяет сопоставить каждый встроенный документ в массиве `«addresses»` с классом Address, а каждое поле `«city»` в этих документах с встроенным адресом с классом City:

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

Каждый из этих трёх типов данных, а также сопоставления конкретных полей можно сопоставить с различными типами PHP. Возможные значения сопоставления:

не указано или NULL (по умолчанию)

  - BSON-массив будет десериализован как PHP-массив (array).
  - BSON-документ (корневой или встроенный) без свойства __pclass [1]

    Свойство __pclass считается существующим, только если существует свойство с таким именем, и оно представляет собой Binary-значение, а подтип Binary значения равен 0x80. Если какое-либо из этих трёх условий не выполняется, свойство __pclass не существует и должно рассматриваться как любое другое обычное свойство.

    становится PHP-объектом stdClass, причём каждый ключ BSON-документа устанавливается как открытое свойство объекта stdClass.
  - BSON-документ (корневой или встроенный) со свойством __pclass [1] становится PHP-объектом имени класса, как это определяет свойство __pclass. Если именованный класс реализует интерфейс MMongoDB\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-объект. Для BSON-объектов, которые содержат свойства __pclass, этот класс будет приоритетным. Если именованный класс не существует, не представляет собой конкретный класс (то есть это абстрактный класс или интерфейс) или не реализует интерфейс MongoDB\BSON\Unserializable, выбрасывается исключение MongoDB\Driver\Exception\InvalidArgumentException. Если BSON-объект содержит свойство __pclass и этот класс существует и реализует интерфейс MongoDB\BSON\Persistable, он заменит класс, представленный в карте типов. Свойства BSON-документа, включая свойство __pclass, если оно существует, отправляются как ассоциативный массив в метод MongoDB\BSON\Unserializable::bsonUnserialize(), чтобы инициализировать свойства объекта.

## TypeMaps

Функции настройки TypeMaps можно установить методом MongoDB\Driver\Cursor::setTypeMap() для объекта MongoDB\Driver\Cursor или аргумента `$typeMap` в методах MongoDB\BSON\toPHP(), MongoDB\BSON\Document::toPHP() и MongoDB\BSON\PackedArray::toPHP(). Каждый из трёх классов (root, document, и array) можно задать индивидуально, в дополнение к типам полей.

Если значение на карте равно NULL, это означает то же, что и значение по умолчанию для этого элемента.

## Примеры

В этих примерах используются следующие классы:

MyClass
  не реализует интерфейсы
YourClass
  реализует интерфейс MongoDB\BSON\Unserializable
OurClass
  реализует интерфейс MongoDB\BSON\Persistable
TheirClass
  расширяет класс OurClass

Метод MongoDB\BSON\Unserializable::bsonUnserialize() классов YourClass, OurClass, OurClass выполняет итерацию по массиву и устанавливает свойства без изменений. Метод также устанавливает для свойства `$unserialized` значение `true`:

    <?php

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

    ?>

    /* typemap: [] (все значения по умолчанию) */
    {"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")}

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open