Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: Review mapping ORM and ODM cookbook #2658

Merged
merged 3 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof 😅

"jmikola/geojson": "^1.0",
"phpbench/phpbench": "^1.0.0",
"phpstan/phpstan": "~1.10.67",
Expand Down
85 changes: 49 additions & 36 deletions docs/en/cookbook/mapping-classes-to-orm-and-odm.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Mapping Classes to the ORM and ODM
==================================

Because of the non-intrusive design of Doctrine, it is possible for you to have plain PHP classes
that are mapped to both a relational database (with the Doctrine2 Object Relational Mapper) and
MongoDB (with the Doctrine MongoDB Object Document Mapper), or any other persistence layer that
Because of the non-intrusive design of Doctrine, it is possible to map PHP
classes to both a relational database (with the Doctrine ORM) and
MongoDB (with the Doctrine MongoDB ODM), or any other persistence layer that
implements the Doctrine Persistence `persistence`_ interfaces.

Test Subject
Expand All @@ -21,18 +21,16 @@ for multiple Doctrine persistence layers:

class BlogPost
{
private $id;
private $title;
private $body;

// ...
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With public properties, we don't need anything more. This comment is a complete waste of space.

public int $id;
public string $title;
public string $body;
}

Mapping Information
-------------------

Now we just need to provide the mapping information for the Doctrine persistence layers so they know
how to consume the objects and persist them to the database.
Now we just need to provide the mapping information for the Doctrine persistence
layers so they know how to consume the objects and persist them to the database.

ORM
~~~
Expand All @@ -48,21 +46,21 @@ First define the mapping for the ORM:
namespace Documents\Blog;

use Documents\Blog\Repository\ORM\BlogPostRepository;
use Doctrine\ORM\Mapping as ORM;

#[Entity(repositoryClass: BlogPostRepository::class)]
#[ORM\Entity(repositoryClass: BlogPostRepository::class)]
class BlogPost
{
#[Id]
#[Column(type: 'int')]
private $id;

#[Column(type: 'string')]
private $title;
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
public int $id;

#[Column(type: 'text')]
private $body;
#[ORM\Column(type: 'string')]
GromNaN marked this conversation as resolved.
Show resolved Hide resolved
public string $title;

// ...
#[ORM\Column(type: 'text')]
public string $body;
}

.. code-block:: xml
Expand All @@ -80,14 +78,15 @@ First define the mapping for the ORM:
</entity>
</doctrine-mapping>

Now you are able to persist the ``Documents\Blog\BlogPost`` with an instance of ``EntityManager``:
Now you are able to persist the ``Documents\Blog\BlogPost`` with an instance of
``EntityManager``:

.. code-block:: php

<?php

$blogPost = new BlogPost();
$blogPost->setTitle('test');
$blogPost->title = 'Hello World!';

$em->persist($blogPost);
$em->flush();
Expand All @@ -98,7 +97,7 @@ You can find the blog post:

<?php

$blogPost = $em->getRepository(BlogPost::class)->findOneBy(array('title' => 'test'));
$blogPost = $em->getRepository(BlogPost::class)->findOneBy(['title' => 'Hello World!']);

MongoDB ODM
~~~~~~~~~~~
Expand All @@ -114,20 +113,19 @@ Now map the same class to the Doctrine MongoDB ODM:
namespace Documents\Blog;

use Documents\Blog\Repository\ODM\BlogPostRepository;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

#[Document(repositoryClass: BlogPostRepository::class)]
#[ODM\Document(repositoryClass: BlogPostRepository::class)]
class BlogPost
{
#[Id]
private $id;
#[ODM\Id(type: 'int', strategy: 'INCREMENT')]
public int $id;

#[Field(type: 'string')]
private $title;
#[ODM\Field]
public string $title;

#[Field(type: 'string')]
private $body;

// ...
#[ODM\Field]
public string $body;
}

.. code-block:: xml
Expand All @@ -145,14 +143,21 @@ Now map the same class to the Doctrine MongoDB ODM:
</document>
</doctrine-mongo-mapping>

Now the same class is able to be persisted in the same way using an instance of ``DocumentManager``:
.. note::

We use the ``INCREMENT`` strategy for the MongoDB ODM for compatibility with
the ORM mapping. But you can also use the default ``AUTO`` strategy
and store a generated MongoDB ObjectId as a string in the SQL database.

Now the same class is able to be persisted in the same way using an instance of
``DocumentManager``:

.. code-block:: php

<?php

$blogPost = new BlogPost();
$blogPost->setTitle('test');
$blogPost->title = 'Hello World!';

$dm->persist($blogPost);
$dm->flush();
Expand All @@ -163,12 +168,13 @@ You can find the blog post:

<?php

$blogPost = $dm->getRepository(BlogPost::class)->findOneBy(array('title' => 'test'));
$blogPost = $dm->getRepository(BlogPost::class)->findOneBy(['title' => 'Hello World!']);

Repository Classes
------------------

You can implement the same repository interface for the ORM and MongoDB ODM easily, e.g. by creating ``BlogPostRepositoryInterface``:
You can implement the same repository interface for the ORM and MongoDB ODM
easily, e.g. by creating ``BlogPostRepositoryInterface``:

.. code-block:: php

Expand Down Expand Up @@ -227,4 +233,11 @@ PHP objects. The data is transparently injected to the objects for you automatic
are not forced to extend some base class or shape your domain in any certain way for it to work
with the Doctrine persistence layers.

.. note::

If the same class is mapped to both the ORM and ODM, and you persist the
instance in both, you will have two separate instances in memory. This is
because the ORM and ODM are separate libraries and do not share the same
object manager.

.. _persistence: https://github.com/doctrine/persistence
28 changes: 28 additions & 0 deletions tests/Documentation/MappingOrmAndOdm/BlogPost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Documentation\MappingOrmAndOdm;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: OrmBlogPostRepository::class)]
#[ORM\Table(name: 'blog_posts')]
#[ODM\Document(repositoryClass: OdmBlogPostRepository::class)]
class BlogPost
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
#[ODM\Id(type: 'int', strategy: 'INCREMENT')]
public int $id;

#[ORM\Column(type: 'string')]
#[ODM\Field]
public string $title;

#[ORM\Column(type: 'text')]
#[ODM\Field]
public string $body;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Documentation\MappingOrmAndOdm;

interface BlogPostRepositoryInterface
{
public function findPostById(int $id): ?BlogPost;
}
75 changes: 75 additions & 0 deletions tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Documentation\MappingOrmAndOdm;

use Doctrine\DBAL\DriverManager;
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;

#[RequiresPhpExtension('pdo_sqlite')]
class MappingOrmAndOdmTest extends BaseTestCase
{
public function testTest(): void
{
// Init ORM
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: [__DIR__],
isDevMode: true,
);
$connection = DriverManager::getConnection([
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add checks and skip this test if pdo_sqlite is not installed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

'driver' => 'pdo_sqlite',
'path' => ':memory:',
], $config);
$connection->executeQuery('CREATE TABLE blog_posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT)');
$em = new EntityManager($connection, $config);

// Init ODM
$dm = $this->dm;

// Create and persist a BlogPost in both ORM and ODM
$blogPost = new BlogPost();
$blogPost->title = 'Hello World!';

$em->persist($blogPost);
$em->flush();
$em->clear();

$dm->persist($blogPost);
$dm->flush();
$dm->clear();

// Load the BlogPost from both ORM and ODM
$ormBlogPost = $em->find(BlogPost::class, $blogPost->id);
$odmBlogPost = $dm->find(BlogPost::class, $blogPost->id);

$this->assertSame($blogPost->id, $ormBlogPost->id);
$this->assertSame($blogPost->id, $odmBlogPost->id);
$this->assertSame($blogPost->title, $ormBlogPost->title);
$this->assertSame($blogPost->title, $odmBlogPost->title);

// Different Object Managers are used, so the instances are different
$this->assertNotSame($odmBlogPost, $ormBlogPost);

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

// Remove the BlogPost from both ORM and ODM using the repository
$ormBlogPostRepository = $em->getRepository(BlogPost::class);
$this->assertInstanceOf(OrmBlogPostRepository::class, $ormBlogPostRepository);
$ormBlogPost = $ormBlogPostRepository->findPostById($blogPost->id);

$odmBlogPostRepository = $dm->getRepository(BlogPost::class);
$this->assertInstanceOf(OdmBlogPostRepository::class, $odmBlogPostRepository);
$odmBlogPost = $odmBlogPostRepository->findPostById($blogPost->id);

$this->assertSame($blogPost->title, $ormBlogPost->title);
$this->assertSame($blogPost->title, $odmBlogPost->title);

// Different Object Managers are used, so the instances are different
$this->assertNotSame($odmBlogPost, $ormBlogPost);
}
}
15 changes: 15 additions & 0 deletions tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Documentation\MappingOrmAndOdm;

use Doctrine\ODM\MongoDB\Repository\DocumentRepository;

final class OdmBlogPostRepository extends DocumentRepository implements BlogPostRepositoryInterface
{
public function findPostById(int $id): ?BlogPost
{
return $this->findOneBy(['id' => $id]);
}
}
15 changes: 15 additions & 0 deletions tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Documentation\MappingOrmAndOdm;

use Doctrine\ORM\EntityRepository;

final class OrmBlogPostRepository extends EntityRepository implements BlogPostRepositoryInterface
{
public function findPostById(int $id): ?BlogPost
{
return $this->findOneBy(['id' => $id]);
}
}
Loading