Статьи

PHP-объекты в MongoDB с Doctrine

Объект-документ Картопостроитель эквивалентно объектно-реляционного Mapper, но с его целями являются документы базы данных NoSQL вместо строк таблицы. Никто не говорил, что Data Mapper всегда должен полагаться на реляционную базу данных в качестве своей серверной части.

В мире PHP, вероятно, ODM Doctrine для MongoDB является наиболее успешным. Это следует за уникальностью Mongo, который является переходным продуктом между SQL и NoSQL, все еще основанным на некоторых реляционных концепциях, таких как запросы.

Много возможностей

Doctrine Mongo ODM поддерживает отображение объектов с помощью аннотаций, помещенных в исходный код класса, или с помощью внешних файлов XML или YAML. В этом и во многих аспектах он основан на тех же принципах, что и Doctrine ORM: в нем есть объект Facade DocumentManager и единица работы, которая объединяет изменения в базе данных при добавлении в нее объектов.

Кроме того, поддерживаются два разных типа отношений между объектами: ссылки и внедренные документы . Первый является эквивалентом классического указателя на другую строку, в которую ORM всегда преобразует ссылки на объекты; второй фактически хранит объект внутри другого, как вы сделали бы с объектом значения. Таким образом, по крайней мере, в случае с Doctrine, проще отображать объекты как документы, а не как строки.

Как было сказано ранее, ODM заимствует некоторые концепции и классы из ORM, в частности из пакета Doctrine \ Common, который имеет стандартный класс коллекции. Поэтому, если вы создали объекты, отображенные с помощью Doctrine ORM, то ничего не изменится для сохранения их в MongoDB, кроме самих метаданных отображения.

преимущества

Если ORM иногда является негерметичной абстракцией, ODM, вероятно, становится проблемой реже. У него меньше накладных расходов, чем у ORM , поскольку нет схемы для определения, а возможность встраивания объектов означает, что не должно быть никаких компромиссов между объектной моделью и возможностями базы данных . Сколько раз мы отказывались от введения потенциального объекта значения из-за сложности его сохранения?

Случай для ODM поверх простого объекта подключения Mongo легко сделать: вы все равно сможете использовать объекты с правильной инкапсуляцией (например, частными полями и ассоциациями) и поведением (много методов) вместо извлечения только пакета JSON из вашей базы данных. ,

Установка

Обязательным условием для ODM является наличие расширения mongo, которое можно установить через pecl .

Убедившись, что расширение присутствует, возьмите Doctrine \ Common как пакет 2.2.x и zip проектов doctrine-mongodb и doctrine-mongodb-odm из Github. Распакуйте все в папку Doctrine /.

После настройки автозагрузки для классов в Doctrine \ используйте этот загрузчик, чтобы получить DocumentManager (эквивалент EntityManager):

use Doctrine\Common\Annotations\AnnotationReader,
    Doctrine\ODM\MongoDB\DocumentManager,
    Doctrine\MongoDB\Connection,
    Doctrine\ODM\MongoDB\Configuration,
    Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;

    private function getADm()
    {
        $config = new Configuration();
        $config->setProxyDir(__DIR__ . '/mongocache');
        $config->setProxyNamespace('MongoProxies');

        $config->setDefaultDB('test');
        $config->setHydratorDir(__DIR__ . '/mongocache');
        $config->setHydratorNamespace('MongoHydrators');

        $reader = new AnnotationReader();
        $config->setMetadataDriverImpl(new AnnotationDriver($reader, __DIR__ . '/Documents'));

        return DocumentManager::create(new Connection(), $config);
    }

Вы сможете вызывать persist () и flush () в DocumentManager вместе с набором других методов для запросов, таких как find () и getRepository ().

Интеграция с ORM

Мы исследуем решение для создания версий объектов, отображаемых с помощью Doctrine ORM. Делать это со столбцом версии было бы инвазивно, а также странно, когда задействовано несколько объектов (вы делаете версию просто корнем графа объектов? Дублируйте другие, когда они меняются? Как вы можете это обнаружить?) Идея состоит в том, чтобы снимок и помещение его в экземпляр MongoDB только для чтения, где все предыдущие версии могут быть получены позднее для аудита (по деловым причинам).

Это было проверено, чтобы быть технически возможным: DocumentManager и EntityManager — это полностью отдельные графы объектов, поэтому они не будут конфликтовать друг с другом. Единственная точка конфликта — это аннотации классов моделей, поскольку оба используют разные версии @Id и могут видеть аннотации других, такие как @Entity и @Document, при разборе.

Это можно решить, используя псевдонимы для всех аннотаций, используя их базовое имя родительского пространства имен в качестве префикса:

<?php
namespace Documents;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
// class name example: Doctrine\ODM\MongoDB\Mapping\Annotations\Id
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;

/**
 * @ODM\Document
 * @Entity
 */
class Car
{
    /**
     * @ODM\Id(strategy="AUTO")
     */
    private $document_id;

    /**
     * @Id @Column(type="integer") @GeneratedValue
     */
    private $id;

    /**
     * @ODM\Field
     * @Column
     */
    private $model;

    public function __construct($model)
    {
        $this->model = $model;
    }

    public function __toString()
    {
        return "Car #$this->document_id: $this->id, $this->model";
    }
}

Это позволяет нам сохранять копию объекта ORM в Mongo:

        $car = new Car('Ford');
        $this->em->persist($car);
        $this->em->flush();
        $this->dm->persist($car);
        $this->dm->flush();
        var_dump($car->__toString());
        $this->assertTrue(strlen($car->__toString()) > 20);

Выходные данные этого теста:

.string(38) "Car #4f61a8322f762f1121000000: 3, Ford"

When retrieving the object, one of the two ids will be null as it is ignored by the ORM or ODM. I am not using the same field because I want to store multiple copies of a row, so it’s id alone won’t be unique. If you’re interested, checkout my hack on Github.

It contains the running example presented in this post. Remember to create the relational schema with:

$ php doctrine.php orm:schema-tool:create

before running the test with

phpunit --bootstrap bootstrap.php DoubleMappingTest.php

MongoDB won’t need the schema setup, of course. There are still some use cases to test, like the behavior in the presence of proxies, but it seems that non-invasive approach of Data Mappers like Doctrine 2 is paying off: try mapping an object in multiple database with Active Records.