Blending the Doctrine ORM and MongoDB ODM post

Posted on 2010-08-25 by jwage


Since the start of the Doctrine MongoDB Object Document Mapper project people have asked how it can be integrated with the ORM. This blog post demonstrates how you can integrate the two transparently, maintaining a clean domain model.

This example will have a Product that is stored in MongoDB and the Order stored in a MySQL database.

Defining our Document and Entity

First lets define our Product document:

namespace Documents;

/** @Document */
class Product
{
    /** @Id */
    private $id;

    /** @String */
    private $title;

    public function getId()
    {
        return $this->id;
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }
}

Next create the Order entity that has a $product and $productId property linking it to the Product that is stored with MongoDB:

namespace Entities;

use Documents\Product;

/**
 * @Entity
 * @Table(name="orders")
 * @HasLifecycleCallbacks
 */
class Order
{
    /**
     * @Id @Column(type="integer")
     * @GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @Column(type="string")
     */
    private $productId;

    /**
     * @var Documents\Product
     */
    private $product;

    public function getId()
    {
        return $this->id;
    }

    public function getProductId()
    {
        return $this->productId;
    }

    public function setProduct(Product $product)
    {
        $this->productId = $product->getId();
        $this->product = $product;
    }

    public function getProduct()
    {
        return $this->product;
    }
}

Event Subscriber

Now we need to setup an event subscriber that will set the $product property of all Order instances to a reference to the document product so it can be lazily loaded when it is accessed the first time. So first register a new event subscriber:

$eventManager = $em->getEventManager();
$eventManager->addEventListener(
    array(\Doctrine\ORM\Events::postLoad), new MyEventSubscriber($dm)
);

So now we need to define a class named MyEventSubscriber and pass a dependency to the DocumentManager. It will have a postLoad() method that sets the product document reference:

use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ORM\Event\LifecycleEventArgs;

class MyEventSubscriber
{
    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
    }

    public function postLoad(LifecycleEventArgs $eventArgs)
    {
        $order = $eventArgs->getEntity();
        $em = $eventArgs->getEntityManager();
        $productReflProp = $em->getClassMetadata('Entities\Order')
            ->reflClass->getProperty('product');
        $productReflProp->setAccessible(true);
        $productReflProp->setValue(
            $order, $this->dm->getReference('Documents\Product', $order->getProductId())
        );
    }
}

The postLoad method will be invoked after an ORM entity is loaded from the database. This allows us to use the DocumentManager to set the $product property with a reference to the Product document with the product id we previously stored.

First create a new Product:

$product = new \Documents\Product();
$product->setTitle('Test Product');
$dm->persist($product);
$dm->flush();

Now create a new Order and link it to a Product in MySQL:

$order = new \Entities\Order();
$order->setProduct($product);
$em->persist($order);
$em->flush();

Later we can retrieve the entity and lazily load the reference to the document in MongoDB:

$order = $em->find('Order', $order->getId());

// Instance of an uninitialized product proxy
$product = $order->getProduct();

// Initializes proxy and queries the database
echo "Order Title: " . $product->getTitle();

If you were to print the $order you would see that we got back regular PHP objects:

print_r($order);

The above would output the following:

Order Object
(
    [id:Entities\Order:private] => 53
    [productId:Entities\Order:private] => 4c74a1868ead0ed7a9000000
    [product:Entities\Order:private] => Proxies\DocumentsProductProxy Object
        (
            [__isInitialized__] => 1
            [id:Documents\Product:private] => 4c74a1868ead0ed7a9000000
            [title:Documents\Product:private] => Test Product
        )

)

That is it! It is not a very abstract example right now but it demonstrates how to utilize the events to do some very interesting things with the Doctrine persistence libraries! I hope that now someone will inspired to create an extension that offers an abstract solution for blending the ORM and ODM together!

Categories: articles