From 38ab8d2aaa76641e6943ccb9c264948ac322ee83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 26 Jun 2024 18:08:44 +0200 Subject: [PATCH 1/3] Review mapping ORM and ODM cookbook --- .../mapping-classes-to-orm-and-odm.rst | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst b/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst index fbf540c73..bb0b3c535 100644 --- a/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst +++ b/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst @@ -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 @@ -21,18 +21,16 @@ for multiple Doctrine persistence layers: class BlogPost { - private $id; - private $title; - private $body; - - // ... + public string $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 ~~~ @@ -48,21 +46,20 @@ 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: 'int')] + public string $id; - #[Column(type: 'text')] - private $body; + #[ORM\Column(type: 'string')] + public string $title; - // ... + #[ORM\Column(type: 'text')] + public string $body; } .. code-block:: xml @@ -80,14 +77,15 @@ First define the mapping for the ORM: -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 setTitle('test'); + $blogPost->title = 'test'; $em->persist($blogPost); $em->flush(); @@ -98,7 +96,7 @@ You can find the blog post: getRepository(BlogPost::class)->findOneBy(array('title' => 'test')); + $blogPost = $em->getRepository(BlogPost::class)->findOneBy(['title' => 'test']); MongoDB ODM ~~~~~~~~~~~ @@ -114,20 +112,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; - - #[Field(type: 'string')] - private $title; + #[ODM\Id] + public string $id; - #[Field(type: 'string')] - private $body; + #[ODM\Field] + public string $title; - // ... + #[ODM\Field] + public string $body; } .. code-block:: xml @@ -152,7 +149,7 @@ Now the same class is able to be persisted in the same way using an instance of setTitle('test'); + $blogPost->title = 'test'; $dm->persist($blogPost); $dm->flush(); @@ -163,12 +160,13 @@ You can find the blog post: getRepository(BlogPost::class)->findOneBy(array('title' => 'test')); + $blogPost = $dm->getRepository(BlogPost::class)->findOneBy(['title' => 'test']); 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 From 748d082ddab079fb18f7c50568840d169efaa308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 27 Jun 2024 10:15:13 +0200 Subject: [PATCH 2/3] Add tests on mapping ORM and ODM --- composer.json | 1 + .../mapping-classes-to-orm-and-odm.rst | 35 ++++++--- .../MappingOrmAndOdm/BlogPost.php | 28 +++++++ .../BlogPostRepositoryInterface.php | 10 +++ .../MappingOrmAndOdm/MappingOrmAndOdmTest.php | 73 +++++++++++++++++++ .../OdmBlogPostRepository.php | 15 ++++ .../OrmBlogPostRepository.php | 15 ++++ 7 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 tests/Documentation/MappingOrmAndOdm/BlogPost.php create mode 100644 tests/Documentation/MappingOrmAndOdm/BlogPostRepositoryInterface.php create mode 100644 tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php create mode 100644 tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php create mode 100644 tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php diff --git a/composer.json b/composer.json index c937390cf..fdcf7c9a2 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst b/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst index bb0b3c535..4d6fd9088 100644 --- a/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst +++ b/docs/en/cookbook/mapping-classes-to-orm-and-odm.rst @@ -21,7 +21,7 @@ for multiple Doctrine persistence layers: class BlogPost { - public string $id; + public int $id; public string $title; public string $body; } @@ -52,8 +52,9 @@ First define the mapping for the ORM: class BlogPost { #[ORM\Id] - #[ORM\Column(type: 'int')] - public string $id; + #[ORM\Column(type: 'integer')] + #[ORM\GeneratedValue(strategy: 'AUTO')] + public int $id; #[ORM\Column(type: 'string')] public string $title; @@ -85,7 +86,7 @@ Now you are able to persist the ``Documents\Blog\BlogPost`` with an instance of title = 'test'; + $blogPost->title = 'Hello World!'; $em->persist($blogPost); $em->flush(); @@ -96,7 +97,7 @@ You can find the blog post: getRepository(BlogPost::class)->findOneBy(['title' => 'test']); + $blogPost = $em->getRepository(BlogPost::class)->findOneBy(['title' => 'Hello World!']); MongoDB ODM ~~~~~~~~~~~ @@ -117,8 +118,8 @@ Now map the same class to the Doctrine MongoDB ODM: #[ODM\Document(repositoryClass: BlogPostRepository::class)] class BlogPost { - #[ODM\Id] - public string $id; + #[ODM\Id(type: 'int', strategy: 'INCREMENT')] + public int $id; #[ODM\Field] public string $title; @@ -142,14 +143,21 @@ Now map the same class to the Doctrine MongoDB ODM: -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 title = 'test'; + $blogPost->title = 'Hello World!'; $dm->persist($blogPost); $dm->flush(); @@ -160,7 +168,7 @@ You can find the blog post: getRepository(BlogPost::class)->findOneBy(['title' => 'test']); + $blogPost = $dm->getRepository(BlogPost::class)->findOneBy(['title' => 'Hello World!']); Repository Classes ------------------ @@ -225,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 diff --git a/tests/Documentation/MappingOrmAndOdm/BlogPost.php b/tests/Documentation/MappingOrmAndOdm/BlogPost.php new file mode 100644 index 000000000..67d00ecff --- /dev/null +++ b/tests/Documentation/MappingOrmAndOdm/BlogPost.php @@ -0,0 +1,28 @@ + '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); + } +} diff --git a/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php b/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php new file mode 100644 index 000000000..5002d17eb --- /dev/null +++ b/tests/Documentation/MappingOrmAndOdm/OdmBlogPostRepository.php @@ -0,0 +1,15 @@ +findOneBy(['id' => $id]); + } +} diff --git a/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php b/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php new file mode 100644 index 000000000..58238b682 --- /dev/null +++ b/tests/Documentation/MappingOrmAndOdm/OrmBlogPostRepository.php @@ -0,0 +1,15 @@ +findOneBy(['id' => $id]); + } +} From 4d481338111d40cbd54782bc5d4261c039c77b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 28 Jun 2024 11:58:49 +0200 Subject: [PATCH 3/3] ORM test requires pdo_sqlite --- tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php b/tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php index a2ac568b2..41f89df39 100644 --- a/tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php +++ b/tests/Documentation/MappingOrmAndOdm/MappingOrmAndOdmTest.php @@ -8,7 +8,9 @@ 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