## Sérialisation et désérialisation des variables PHP dans MongoDB

Ce document explique comment les structures composées (c'est-à-dire les documents, les tableaux et les objets) sont converties entre les valeurs BSON et PHP.

## Sérialisation en BSON

## Tableaux

Si un tableau est un tableau compact — c'est-à-dire un tableau vide ou les clés commencent à 0 et sont séquentielles sans trous : tableau BSON.

Si le tableau n'est pas compact — c'est-à-dire qu'il a des clés associatives (chaînes), que les clés ne commencent pas à 0, ou qu'il y a des trous : objet BSON.

Un document de niveau supérieur (racine), toujours sérialisé en tant que document BSON.

## Exemples

Ces exemples sérialisent en tant que tableau BSON :

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

Ces exemples sérialisent en tant qu'objet 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 }

Il est à noter que les cinq exemples sont des extraits d'un document complet, et ne représentent qu'une valeur à l'intérieur d'un document.

## Objets

Si un objet est de la classe stdClass, sérialiser en tant que document BSON.

Si un objet est une classe supportée qui implémente MongoDB\BSON\Type, alors utiliser la logique de sérialisation BSON pour ce type spécifique. Les instances de MongoDB\BSON\Type (à l'exclusion de MongoDB\BSON\Serializable) ne peuvent être sérialisées que comme valeur de champ de document. Tenter de sérialiser un tel objet en tant que document racine lèvera une MongoDB\Driver\Exception\UnexpectedValueException.

Si un objet est d'une classe inconnue implémentant l'interface MongoDB\BSON\Type, alors une MongoDB\Driver\Exception\UnexpectedValueException est lancée.

Si un objet est d'une classe autre, sans implémenter une interface spéciale, sérialiser en tant que document BSON. Garder seulement les propriétés public, et ignorer les propriétés protected et private.

Si un objet est d'une classe qui implémente MongoDB\BSON\Serializable, appeler MongoDB\BSON\Serializable::bsonSerialize() et utiliser le tableau ou stdClass retourné pour sérialiser en tant que document BSON ou tableau. Le type BSON sera déterminé par les règles suivantes :

1. Les documents racines doivent être sérialisés en tant que document BSON.
2. Les objets MongoDB\BSON\Persistable doivent être sérialisés en tant que document BSON.
3. Si MongoDB\BSON\Serializable::bsonSerialize() retourne un tableau compact, sérialiser en tant que tableau BSON.
4. Si MongoDB\BSON\Serializable::bsonSerialize() retourne un tableau non-compact ou stdClass, sérialiser en tant qu'objet BSON.
5. Si MongoDB\BSON\Serializable::bsonSerialize() ne retourne pas un tableau ou stdClass, lance une exception MongoDB\Driver\Exception\UnexpectedValueException.

Si un objet est d'une classe qui implémente l'interface MongoDB\BSON\Persistable (qui implique MongoDB\BSON\Serializable), obtenir les propriétés de manière similaire aux paragraphes précédents, mais aussi ajouter une propriété __pclass en tant que valeur binaire, avec un sous-type `0x80` et des données portant le nom de la classe entièrement qualifié de l'objet qui est sérialisé.

La propriété __pclass est ajoutée au tableau ou à l'objet retourné par MongoDB\BSON\Serializable::bsonSerialize(), ce qui signifie qu'elle écrasera toute clé/propriété __pclass dans la valeur de retour de MongoDB\BSON\Serializable::bsonSerialize(). Si l'on veut éviter ce comportement et définir sa propre valeur __pclass, il ne faut pas implémenter MongoDB\BSON\Persistable et devriez plutôt implémenter MongoDB\BSON\Serializable directement.

## Exemples

    <?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"} en tant que classe racine
      //        ["foo", "bar"] en tant que valeur imbriquée

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

    ?>

## Deserialization from BSON

Avertissement

Les documents BSON peuvent techniquement contenir des clés dupliquées car les documents sont stockés en tant qu'une liste de paire clé-valeur ; cependant, les applications devrait s'abstenir de générer des documents avec des clés dupliquées car le comportement du serveur et du pilote peut être indéfinie. Puisque les objets et tableaux PHP ne peuvent pas avoir de clés dupliquées, les données pourraient aussi être perdu lors du décodage d'un document BSON avec des clés dupliquées.

L'ancienne extension `mongo` désérialisait les documents BSON et les tableaux BSON en tant que tableaux PHP. Tant que les tableaux PHP sont pratiques à utiliser, ce comportement était problématique car différents types BSON pouvaient être désérialisés en la même valeur PHP (par exemple `{"0": "foo"}` et `["foo"]`) et rendait impossible d'inférer le type BSON original. Par défaut, l'extension `mongodb` adresse cette préoccupation en s'assurant que les tableaux BSON et les documents BSON sont convertis en tableaux et objets PHP, respectivement.

Pour les types composés, il existe trois types de données :

root
  réfère à un document BSON de niveau supérieur seulement
document
  réfère à des documents BSON imbriqués seulement
array
  réfère à un tableau BSON

A part les trois types collectifs, il est aussi possible de configurer des champs spécifiques dans le document pour mapper les types de données mentionnés ci-dessous. Par exemple, le type de carte suivant permet de mapper chaque document intégré dans un tableau `"addresses"` à une classe Address et chaque champ `"city"` dans ces documents d'adresse intégrés à une classe City:

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

Chacun de ces trois types de données, ainsi que les mappages spécifiques aux champs, peuvent être mappés contre différents types PHP. Les valeurs de mappage possibles sont:

non défini ou NULL (par défaut)

  - Un tableau BSON sera désérialisé en un array PHP.
  - Un document BSON (racine ou imbriqué) sans propriété __pclass [1]

    Une propriété __pclass n'est considérée comme existante que si une propriété portant ce nom existe, et qu'elle est une valeur binaire, et que le sous-type de la valeur binaire est 0x80. Si une de ces trois conditions n'est pas remplie, la propriété __pclass n'existe pas et doit être traitée comme toute autre propriété normale.

    devient un objet stdClass, avec chaque clé de document BSON définie comme une propriété de stdClass publique.
  - Un document BSON (racine ou imbriqué) avec une propriété __pclass [1] devient un objet PHP de la classe nommée par la propriété __pclass. Si la classe nommée implémente l'interface MongoDB\BSON\Persistable, alors les propriétés du document BSON, y compris la propriété __pclass, sont envoyées sous forme de tableau associatif à la fonction MongoDB\BSON\Unserializable::bsonUnserialize() pour initialiser les propriétés de l'objet. Si la classe nommée n'existe pas ou n'implémente pas l'interface MongoDB\BSON\Persistable, stdClass sera utilisé et chaque clé de document BSON (y compris __pclass) sera définie comme une propriété publique de stdClass. La fonctionnalité __pclass repose sur le fait que la propriété soit partie d'un document MongoDB récupéré. Si l'on utilise une projection lors de la recherche de documents, il faut inclure le champ __pclass dans la projection pour que cette fonctionnalité fonctionne.
`"array"`
  Transforme un tableau BSON en un array PHP. Il n'y aura pas de traitement spécial d'une propriété __pclass [1] mais elle peut être définie comme un élément dans le tableau retourné si elle était présente dans le document BSON.
`"object"` ou `"stdClass"`
  Transforme un tableau BSON ou un document BSON en un objet stdClass. Il n'y aura pas de traitement spécial d'une propriété __pclass [1] mais elle peut être définie comme une propriété publique dans l'objet retourné si elle était présente dans le document BSON.
`"bson"`
  Transforme un tableau BSON en un MongoDB\BSON\PackedArray et un document BSON en un MongoDB\BSON\Document, indépendamment du fait que le document BSON ait une propriété __pclass [1].

  > Note: La valeur `bson` n'est disponible que pour les trois types racines, et non dans les mappages spécifiques aux champs.

toutes les autres chaînes de caractères
  Définit le nom de la classe à laquelle le document BSON doit être désérialisé. Pour les documents BSON qui incluent des propriétés __pclass, cette classe prendra la priorité. Si la classe nommée n'existe pas, n'est pas concrète (c'est-à-dire qu'elle est abstraite ou une interface), ou n'implémente pas l'interface MongoDB\BSON\Unserializable, une exception MongoDB\Driver\Exception\InvalidArgumentException est lancée. Si l'objet BSON a une propriété __pclass et que cette classe existe et implémente MongoDB\BSON\Persistable, elle prendra le pas sur la classe fournie dans la carte de type. Les propriétés du document BSON, y compris la propriété __pclass, seront envoyées sous forme de tableau associatif à la fonction MongoDB\BSON\Unserializable::bsonUnserialize() pour initialiser les propriétés de l'objet.

## TypeMaps

Les TypeMaps peuvent être définis via la méthode MongoDB\Driver\Cursor::setTypeMap() sur un objet MongoDB\Driver\Cursor, ou l'argument `$typeMap` de MongoDB\BSON\toPHP(), MongoDB\BSON\Document::toPHP(), et MongoDB\BSON\PackedArray::toPHP(). Chacune des trois classes (racine, document, et array) peut être définie individuellement, en plus des types spécifiques aux champs.

Si la valeur dans le TypeMap est NULL, cela signifie la même chose que la valeur par défaut pour cet élément.

## Exemples

Ces exemples utilisent les classes suivantes:

MyClass
  qui n'implémente aucune interface
YourClass
  qui implémente MongoDB\BSON\Unserializable
OurClass
  qui implémente MongoDB\BSON\Persistable
TheirClass
  qui étend OurClass

La méthode MongoDB\BSON\Unserializable::bsonUnserialize() d'YourClass, OurClass, TheirClass itère sur le tableau et définit les propriétés sans modifications. Elle ajoute aussi la propriété `$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") }

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