DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • MongoDB to Couchbase for Developers, Part 1: Architecture
  • Introduction to Couchbase for Oracle Developers and Experts: Part 2 - Database Objects
  • MongoDB to Couchbase: An Introduction to Developers and Experts
  • FHIR Data Model With Couchbase N1QL

Trending

  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  • AI Speaks for the World... But Whose Humanity Does It Learn From?
  • Driving DevOps With Smart, Scalable Testing
  • Ensuring Configuration Consistency Across Global Data Centers
  1. DZone
  2. Data Engineering
  3. Databases
  4. PHP objects in MongoDB with Doctrine

PHP objects in MongoDB with Doctrine

By 
Giorgio Sironi user avatar
Giorgio Sironi
·
Mar. 20, 12 · Interview
Likes (0)
Comment
Save
Tweet
Share
21.9K Views

Join the DZone community and get the full member experience.

Join For Free

An is equivalent to an Object-Relational Mapper, but with its targets are documents of a NoSQL database instead of table rows. No one said that a Data Mapper must always rely on a relational database as its back end.

In the PHP world, probably the Doctrine ODM for MongoDB is the most successful. This followes to the opularity of Mongo, which is a transitional product between SQL and NoSQL, still based on some relational concepts like queries.

Lots of features

The Doctrine Mongo ODM supports mapping of objects via annotations placed in the class source code, or via external XML or YAML files. In this and in many aspects it is based on the same concepts as the Doctrine ORM: it features a Facade DocumentManager object and a Unit Of Work that batches changes to the database when objects are added to it.

Moreover, two different types of relationships between objects are supported: references and embedded documents. The first is the equivalent of the classical pointer to another row which ORM always transform object references into; the second actually stores an object inside another one, like you would do with a Value Object. Thus, at least in Doctrine's case, it is easier to map objects as documents that as rows.

As said before, the ODM borrows some concepts and classes from the ORM, in particular from the Doctrine\Common package which features a standard collection class. So if you have built objects mapped with the Doctrine ORM nothing changes for persisting them in MongoDB, except for the mapping metadata itself.

Advantages

If an ORM is sometimes a leaky abstraction, an ODM probably becomes an issue less often. It has less overhead than an ORM, since there is no schema to define and the ability to embed objects means there should be no compromises between the object model and the capabilities of the database. How many times we have renounced introducing a potential Value Object because of the difficulty in persisting it?

The case for an ODM over a plain Mongo connection object is easy to make: you will still be able to use objects with proper encapsulation (like private fields and associations) and behavior (many methods) instead of extracting just a JSON package from your database.

Installation

A prerequisite for the ODM is the presence of the mongo extension, that can be installed via pecl.

After having verified the extension is present, grab the Doctrine\Common as the 2.2.x package, and a zip of the doctrine-mongodb and doctrine-mongodb-odm projects from Github. Decompress everything into a Doctrine/ folder.

After having setup autoloading for classes in Doctrine\, use this bootstrap to get a DocumentManager (the equivalent of 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);
    }


You will be able to call persist() and flush() on the DocumentManager, along with a set of other methods for querying like find() and getRepository().

Integration with an ORM

We are researching a solution for versioning objects mapped with the Doctrine ORM. Doing this with a version column would be invasive, and also strange where multiple objects are involved (do you version just the root of an object graph? Duplicate the other ones when they change? How can you detect that?) The idea is taking a snapshot and putting it in a read only MongoDB instance, where all previous versions can be retrieved later for auditing (business reasons).

This has been verified to be technically possible: the DocumentManager and EntityManager are totally separate object graphs, so they won't clash with each other. The only point of conflict is the annotations of model classes, since both use different version of @Id, and can see the other's annotation like @Entity and @Document while parsing.

This can be solved by using aliases for all the annotations, using their parent namespace basename as a prefix:

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

This make us able to save a copy of an ORM object into 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);

The output produces by this test is:

.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.

Object (computer science) Database PHP Doctrine (PHP) Relational database MongoDB sql

Opinions expressed by DZone contributors are their own.

Related

  • MongoDB to Couchbase for Developers, Part 1: Architecture
  • Introduction to Couchbase for Oracle Developers and Experts: Part 2 - Database Objects
  • MongoDB to Couchbase: An Introduction to Developers and Experts
  • FHIR Data Model With Couchbase N1QL

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!