Skip to content

Commit

Permalink
Review cookbook on blending ORM and ODM
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Jun 26, 2024
1 parent 1408466 commit 98f9a62
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 47 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"ext-bcmath": "*",
"doctrine/annotations": "^1.12 || ^2.0",
"doctrine/coding-standard": "^12.0",
"doctrine/orm": "^3.2",
"jmikola/geojson": "^1.0",
"phpbench/phpbench": "^1.0.0",
"phpstan/phpstan": "~1.10.67",
Expand All @@ -63,8 +64,8 @@
"psr-4": {
"Doctrine\\ODM\\MongoDB\\Benchmark\\": "benchmark",
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
"Documentation\\": "tests/Documentation",
"Documents\\": "tests/Documents",
"Documents81\\": "tests/Documents81",
"Stubs\\": "tests/Stubs",
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
}
Expand Down
85 changes: 39 additions & 46 deletions docs/en/cookbook/blending-orm-and-mongodb-odm.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
Blending the ORM and MongoDB ODM
================================

Since the start of the `Doctrine MongoDB Object Document Mapper`_ project people have asked how it can be integrated with the `ORM`_. This article will demonstrates how you can integrate the two transparently, maintaining a clean domain model.
Since the start of the `Doctrine MongoDB Object Document Mapper`_ project people
have asked how it can be integrated with the `ORM`_. This article will
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.
This example will have a ``Product`` that is stored in MongoDB and the ``Order``
stored in a SQL database like MySQL, PostgreSQL or SQLite.

Define Product
--------------
Expand All @@ -20,31 +24,17 @@ First lets define our ``Product`` document:
class Product
{
#[Id]
private $id;
public string $id;
#[Field(type: 'string')]
private $title;
public function getId(): ?string
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public string $title;
}
Define Entity
-------------

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

.. code-block:: php
Expand All @@ -61,15 +51,12 @@ Next create the ``Order`` entity that has a ``$product`` and ``$productId`` prop
#[Id]
#[Column(type: 'int')]
#[GeneratedValue(strategy: 'AUTO')]
private $id;
public int $id;
#[Column(type: 'string')]
private $productId;
private string $productId;
/**
* @var Documents\Product
*/
private $product;
private Product $product;
public function getId(): ?int
{
Expand All @@ -96,7 +83,10 @@ Next create the ``Order`` entity that has a ``$product`` and ``$productId`` prop
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:
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:

.. code-block:: php
Expand All @@ -107,15 +97,17 @@ Now we need to setup an event subscriber that will set the ``$product`` property
[\Doctrine\ORM\Events::postLoad], new MyEventSubscriber($dm)
);
or in .yaml
or in YAML configuration of the Symfony container:

.. code-block:: yaml
App\Listeners\MyEventSubscriber:
tags:
- { name: doctrine.event_listener, connection: default, event: postLoad}
- { name: doctrine.event_listener, connection: default, event: postLoad }
So now we need to define a class named ``MyEventSubscriber`` and pass ``DocumentManager`` as a dependency. It will have a ``postLoad()`` method that sets the product document reference:
So now we need to define a class named ``MyEventSubscriber`` and pass
``DocumentManager`` as a dependency. It will have a ``postLoad()`` method that
sets the product document reference:

.. code-block:: php
Expand All @@ -126,10 +118,9 @@ So now we need to define a class named ``MyEventSubscriber`` and pass ``Document
class MyEventSubscriber
{
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
public function __construct(
private readonly DocumentManager $dm,
) {}
public function postLoad(LifecycleEventArgs $eventArgs): void
{
Expand All @@ -139,13 +130,13 @@ So now we need to define a class named ``MyEventSubscriber`` and pass ``Document
return;
}
$em = $eventArgs->getEntityManager();
$productReflProp = $em->getClassMetadata(Order::class)
->reflClass->getProperty('product');
$productReflProp->setAccessible(true);
$productReflProp->setValue(
$order, $this->dm->getReference(Product::class, $order->getProductId())
);
$product = $this->dm->getReference(Product::class, $order->getProductId());
$eventArgs->getObjectManager()
->getClassMetadata(Order::class)
->reflClass
->getProperty('product')
->setValue($order, $product);
}
}
Expand All @@ -165,7 +156,7 @@ First create a new ``Product``:
<?php
$product = new \Documents\Product();
$product->setTitle('Test Product');
$product->title = 'Test Product';
$dm->persist($product);
$dm->flush();
Expand All @@ -180,19 +171,21 @@ Now create a new ``Order`` and link it to a ``Product`` in MySQL:
$em->persist($order);
$em->flush();
Later we can retrieve the entity and lazily load the reference to the document in MongoDB:
Later we can retrieve the entity and lazily load the reference to the document
in MongoDB:

.. code-block:: php
<?php
$order = $em->find(Order::class, $order->getId());
$order = $em->find(Order::class, $order->id);
$product = $order->getProduct();
echo "Order Title: " . $product->getTitle();
echo "Order Title: " . $product->title;
If you were to print the ``$order`` you would see that we got back regular PHP objects:
If you were to print the ``$order`` you would see that we got back regular PHP
objects:

.. code-block:: php
Expand Down
34 changes: 34 additions & 0 deletions tests/Documentation/BlendingOrm/BlendingOrmEventSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Documentation\BlendingOrm;

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

class BlendingOrmEventSubscriber
{
public function __construct(
private readonly DocumentManager $dm,
) {
}

public function postLoad(PostLoadEventArgs $eventArgs): void
{
$order = $eventArgs->getObject();

if (! $order instanceof Order) {
return;
}

// Reference to the Product document, without loading it
$product = $this->dm->getReference(Product::class, $order->getProductId());

$eventArgs->getObjectManager()
->getClassMetadata(Order::class)
->reflClass
->getProperty('product')
->setValue($order, $product);
}
}
61 changes: 61 additions & 0 deletions tests/Documentation/BlendingOrm/BlendingOrmTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Documentation\BlendingOrm;

use Doctrine\DBAL\DriverManager;
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Events;
use Doctrine\ORM\ORMSetup;

class BlendingOrmTest extends BaseTestCase
{
public function testTest(): void
{
$dm = $this->dm;

// Init ORM
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: [__DIR__],
isDevMode: true,
);
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => ':memory:',
], $config);
$connection->executeQuery('CREATE TABLE orders (id INTEGER PRIMARY KEY, productId TEXT)');
$em = new EntityManager($connection, $config);
$em->getEventManager()
->addEventListener(
[Events::postLoad],
new BlendingOrmEventSubscriber($dm),
);

// Init Product document and Order entity
$product = new Product();
$product->title = 'Test Product';
$dm->persist($product);
$dm->flush();

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

$order = $em->find(Order::class, $order->id);
// The Product document is loaded from the DocumentManager
$this->assertSame($product, $order->getProduct());

$em->clear();
$dm->clear();

$order = $em->find(Order::class, $order->id);
// New Product instance, not the same as the one in the DocumentManager
$this->assertNotSame($product, $order->getProduct());
$this->assertSame($product->id, $order->getProduct()->id);
$this->assertSame($product->title, $order->getProduct()->title);
}
}
42 changes: 42 additions & 0 deletions tests/Documentation/BlendingOrm/Order.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Documentation\BlendingOrm;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Table;

#[Entity]
#[Table(name: 'orders')]
class Order
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue(strategy: 'AUTO')]
public int $id;

#[Column(type: 'string')]
private string $productId;

private Product $product;

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

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

public function getProduct(): ?Product
{
return $this->product;
}
}
19 changes: 19 additions & 0 deletions tests/Documentation/BlendingOrm/Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Documentation\BlendingOrm;

use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id;

#[Document]
class Product
{
#[Id]
public string $id;

#[Field]
public string $title;
}

0 comments on commit 98f9a62

Please sign in to comment.