==================
Modeling BSON Data
==================

.. default-domain:: mongodb

.. contents:: On this page
   :local:
   :backlinks: none
   :depth: 2
   :class: singlecol


Type Maps
---------

Most methods that read data from MongoDB support a ``typeMap`` option, which
allows control over how BSON is converted to PHP. Additionally,
the :phpclass:`MongoDB\\Client`, :phpclass:`MongoDB\\Database`, and
:phpclass:`MongoDB\\Collection` classes accept a ``typeMap`` option, which can
be used to specify a default type map to apply to any supporting methods and
selected classes (e.g. :phpmethod:`MongoDB\\Client::selectDatabase()`).

The :phpclass:`MongoDB\\Client`, :phpclass:`MongoDB\\Database`, and
:phpclass:`MongoDB\\Collection` classes use the following type map by
default:

.. code-block:: php

   [
       'array' => 'MongoDB\Model\BSONArray',
       'document' => 'MongoDB\Model\BSONDocument',
       'root' => 'MongoDB\Model\BSONDocument',
   ]

The type map above will convert BSON documents and arrays to
:phpclass:`MongoDB\\Model\\BSONDocument` and
:phpclass:`MongoDB\\Model\\BSONArray` objects, respectively. The ``root`` and
``document`` keys are used to distinguish the top-level BSON document from
embedded documents, respectively.

A type map may specify any class that implements
:php:`MongoDB\\BSON\\Unserializable <mongodb-bson-unserializable>` as well as
``"array"``, ``"stdClass``", and ``"object"`` (``"stdClass``" and ``"object"``
are aliases of one another).

.. seealso:: :php:`Deserialization from BSON <manual/en/mongodb.persistence.deserialization.php>` in the PHP manual


Persistable Classes
-------------------

The driver's :php:`persistence specification <mongodb.persistence>` outlines how
classes implementing its :php:`MongoDB\\BSON\\Persistable
<mongodb-bson-persistable>` interface are serialized to and deserialized from
BSON. The :php:`Persistable <mongodb-bson-persistable>` interface is analogous
to PHP's :php:`Serializable interface <class.serializable>`.

The driver automatically handles serialization and deserialization for classes
implementing the :php:`Persistable <mongodb-bson-persistable>` interface without
requiring the use of the ``typeMap`` option. This is done by encoding the name
of the PHP class in a special property within the BSON document.

.. note::

   When deserializing a PHP variable from BSON, the encoded class name of a
   :php:`Persistable <mongodb-bson-persistable>` object will override any class
   specified in the type map, but it will not override ``"array"`` and
   ``"stdClass"`` or ``"object"``. This is discussed in the
   :php:`persistence specification <mongodb.persistence>` but it bears
   repeating.

Consider the following class definition:

.. code-block:: php

   <?php

   class Person implements MongoDB\BSON\Persistable
   {
       private MongoDB\BSON\ObjectId $id;
       private string $name;
       private MongoDB\BSON\UTCDateTime $createdAt;

       public function __construct(string $name)
       {
           $this->id = new MongoDB\BSON\ObjectId;
           $this->name = $name;
           $this->createdAt = new MongoDB\BSON\UTCDateTime;
       }

       function bsonSerialize()
       {
           return [
               '_id' => $this->id,
               'name' => $this->name,
               'createdAt' => $this->createdAt,
           ];
       }

       function bsonUnserialize(array $data)
       {
           $this->id = $data['_id'];
           $this->name = $data['name'];
           $this->createdAt = $data['createdAt'];
       }
   }

The following example constructs a ``Person`` object, inserts it into the
database, and reads it back as an object of the same type:

.. code-block:: php

   <?php

   $collection = (new MongoDB\Client)->test->persons;

   $result = $collection->insertOne(new Person('Bob'));

   $person = $collection->findOne(['_id' => $result->getInsertedId()]);

   var_dump($person);

The output would then resemble:

.. code-block:: none

   object(Person)#18 (3) {
     ["id":"Person":private]=>
     object(MongoDB\BSON\ObjectId)#15 (1) {
       ["oid"]=>
       string(24) "56fad2c36118fd2e9820cfc1"
     }
     ["name":"Person":private]=>
     string(3) "Bob"
     ["createdAt":"Person":private]=>
     object(MongoDB\BSON\UTCDateTime)#17 (1) {
       ["milliseconds"]=>
       int(1459278531218)
     }
   }

The same document in the MongoDB shell might display as:

.. code-block:: js

   {
     "_id" : ObjectId("56fad2c36118fd2e9820cfc1"),
     "__pclass" : BinData(128,"UGVyc29u"),
     "name" : "Bob",
     "createdAt" : ISODate("2016-03-29T19:08:51.218Z")
   }

.. note::

   :php:`MongoDB\\BSON\\Persistable <mongodb-bson-persistable>` may only be used
   for root and embedded BSON documents. It may not be used for BSON arrays.
.. _php-type-map:

Working with Enums
------------------

:php:`Backed enums <enumerations.backed>` can be used with BSON and will
serialize as their case value (i.e. integer or string).
:php:`Pure enums <enumerations.basics>`, which have no backed cases, cannot be
directly serialized. This is similar to how enums are handled by
:php:`json_encode() <json-encode>`.

Round-tripping a backed enum through BSON requires special handling. In the
following example, the `bsonUnserialize()` method in the class containing the
enum is responsible for converting the value back to an enum case:

.. code-block:: php

   <?php

   enum Role: int
   {
       case USER = 1;
       case ADMIN = 2;
   }

   class User implements MongoDB\BSON\Persistable
   {
       public function __construct(
           private string $username,
           private Role $role,
           private MongoDB\BSON\ObjectId $_id = new MongoDB\BSON\ObjectId(),
       ) {}

       public function bsonSerialize(): array
       {
           return [
               '_id' => $this->_id,
               'username' => $this->username,
               'role' => $this->role,
           ];
       }

       public function bsonUnserialize(array $data): void
       {
           $this->_id = $data['_id'];
           $this->username = $data['username'];
           $this->role = Role::from($data['role']);
       }
   }

Enums are prohibited from implementing
:php:`MongoDB\\BSON\\Unserializable <mongodb-bson-unserializable>` and
:php:`MongoDB\\BSON\\Persistable <mongodb-bson-persistable>`, since enum cases
have no state and cannot be instantiated like ordinary objects. Pure and backed
enums can, however, implement
:php:`MongoDB\\BSON\\Serializable <mongodb-bson-serializable>`, which can be
used to overcome the default behavior whereby backed enums are serialized as
their case value and pure enums cannot be serialized.
