From 54666acc19e8e706b3c4f0dcdb317e96befb724f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= <lc.fremont@gmail.com>
Date: Tue, 14 Jan 2025 11:36:47 +0100
Subject: [PATCH] Add support for Doctrine ORM 3

---
 .github/workflows/build.yml                     | 17 ++++++++++++++---
 composer.json                                   |  4 ++--
 .../Doctrine/ORM/ContainerRepositoryFactory.php |  8 ++++----
 .../Doctrine/ORM/ResourceRepositoryTrait.php    | 12 ++++++------
 .../ORMMappedSuperClassSubscriber.php           |  1 +
 .../EventListener/ORMTranslatableListener.php   |  6 +++++-
 src/Component/composer.json                     |  4 ++--
 .../Application/src/Entity/GedmoBaseExample.php |  2 --
 .../src/Entity/GedmoExtendedExample.php         |  2 --
 9 files changed, 34 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1f3787a25..664cbf5bb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,16 +13,21 @@ on:
 jobs:
     tests:
         runs-on: ubuntu-latest
-        name: "PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}"
+        name: "PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, ORM ${{ matrix.orm }}"
         env:
             APP_ENV: ${{ matrix.app_env }}
         strategy:
             fail-fast: false
             matrix:
+                orm: ['2.*', '3.*']
                 php: ["8.1", "8.2", "8.3"]
                 composer-flags: ['--no-scripts --prefer-stable --prefer-dist']
                 symfony: ["^6.4", "^7.1"]
                 app_env: ["test"]
+                include:
+                    - php: "8.3"
+                      symfony: "^7.1"
+                      composer-flags: '--no-scripts --prefer-stable --prefer-dist --prefer-lowest'
                 exclude:
                     - php: "8.1"
                       symfony: "^7.1"
@@ -47,6 +52,13 @@ jobs:
                     composer config extra.symfony.require "${{ matrix.symfony }}"
                     (cd src/Component && composer config extra.symfony.require "${{ matrix.symfony }}")
 
+            -
+                name: Restrict ORM version
+                if: matrix.orm != ''
+                run: |
+                    composer require --dev doctrine/orm "${{ matrix.orm }}" --no-update --no-scripts
+                    (cd src/Component && composer require --dev doctrine/orm "${{ matrix.orm }}" --no-update --no-scripts)
+
             -
                 name: Remove hateoas on Symfony 7
                 if: matrix.symfony == '^7.0'
@@ -55,13 +67,12 @@ jobs:
             -
                 name: Install dependencies
                 run: |
-                    composer update --no-scripts
+                    composer update ${{ matrix.composer-flags }}
                     (cd src/Component && composer update ${{ matrix.composer-flags }})
 
             -
                 name: Prepare test application
                 run: |
-                    (cd tests/Application && bin/console doctrine:database:create)
                     (cd tests/Application && bin/console doctrine:schema:create)
 
             -
diff --git a/composer.json b/composer.json
index 58bc55da8..0bbb06db2 100644
--- a/composer.json
+++ b/composer.json
@@ -56,7 +56,7 @@
     },
     "require-dev": {
         "doctrine/doctrine-bundle": "^2.13",
-        "doctrine/orm": "^2.18",
+        "doctrine/orm": "^2.18 || ^3.3",
         "friendsofsymfony/rest-bundle": "^3.7",
         "jms/serializer-bundle": "^3.5 || ^4.0 || ^5.0",
         "lchrusciel/api-test-case": "^5.0",
@@ -89,7 +89,7 @@
         "winzou/state-machine-bundle": "^0.6.2"
     },
     "conflict": {
-        "doctrine/orm": "<2.18 || ^3.0",
+        "doctrine/orm": "<2.18",
         "doctrine/doctrine-bundle": "<2.0 || ^3.0",
         "friendsofsymfony/rest-bundle": "<3.0",
         "jms/serializer-bundle": "<3.5",
diff --git a/src/Bundle/Doctrine/ORM/ContainerRepositoryFactory.php b/src/Bundle/Doctrine/ORM/ContainerRepositoryFactory.php
index 2d0fc21b8..e4f4faa91 100644
--- a/src/Bundle/Doctrine/ORM/ContainerRepositoryFactory.php
+++ b/src/Bundle/Doctrine/ORM/ContainerRepositoryFactory.php
@@ -14,9 +14,9 @@
 namespace Sylius\Bundle\ResourceBundle\Doctrine\ORM;
 
 use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\EntityRepository as DoctrineEntityRepository;
 use Doctrine\ORM\Mapping\ClassMetadata;
 use Doctrine\ORM\Repository\RepositoryFactory;
-use Doctrine\Persistence\ObjectRepository;
 
 final class ContainerRepositoryFactory implements RepositoryFactory
 {
@@ -25,7 +25,7 @@ final class ContainerRepositoryFactory implements RepositoryFactory
     /** @var string[] */
     private array $genericEntities;
 
-    /** @var ObjectRepository[] */
+    /** @var DoctrineEntityRepository[] */
     private array $managedRepositories = [];
 
     /**
@@ -38,7 +38,7 @@ public function __construct(RepositoryFactory $doctrineFactory, array $genericEn
     }
 
     /** @psalm-suppress InvalidReturnType */
-    public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
+    public function getRepository(EntityManagerInterface $entityManager, $entityName): DoctrineEntityRepository
     {
         $metadata = $entityManager->getClassMetadata($entityName);
 
@@ -53,7 +53,7 @@ public function getRepository(EntityManagerInterface $entityManager, $entityName
     private function getOrCreateRepository(
         EntityManagerInterface $entityManager,
         ClassMetadata $metadata,
-    ): ObjectRepository {
+    ): DoctrineEntityRepository {
         $repositoryHash = $metadata->getName() . spl_object_hash($entityManager);
 
         if (!isset($this->managedRepositories[$repositoryHash])) {
diff --git a/src/Bundle/Doctrine/ORM/ResourceRepositoryTrait.php b/src/Bundle/Doctrine/ORM/ResourceRepositoryTrait.php
index 1ad59dded..abd6fad6c 100644
--- a/src/Bundle/Doctrine/ORM/ResourceRepositoryTrait.php
+++ b/src/Bundle/Doctrine/ORM/ResourceRepositoryTrait.php
@@ -32,15 +32,15 @@ trait ResourceRepositoryTrait
 {
     public function add(ResourceInterface $resource): void
     {
-        $this->_em->persist($resource);
-        $this->_em->flush();
+        $this->getEntityManager()->persist($resource);
+        $this->getEntityManager()->flush();
     }
 
     public function remove(ResourceInterface $resource): void
     {
         if (null !== $this->find($resource->getId())) {
-            $this->_em->remove($resource);
-            $this->_em->flush();
+            $this->getEntityManager()->remove($resource);
+            $this->getEntityManager()->flush();
         }
     }
 
@@ -78,7 +78,7 @@ protected function getArrayPaginator($objects): Pagerfanta
     protected function applyCriteria(QueryBuilder $queryBuilder, array $criteria = []): void
     {
         foreach ($criteria as $property => $value) {
-            if (!in_array($property, array_merge($this->_class->getAssociationNames(), $this->_class->getFieldNames()), true)) {
+            if (!in_array($property, array_merge($this->getClassMetadata()->getAssociationNames(), $this->getClassMetadata()->getFieldNames()), true)) {
                 continue;
             }
 
@@ -101,7 +101,7 @@ protected function applyCriteria(QueryBuilder $queryBuilder, array $criteria = [
     protected function applySorting(QueryBuilder $queryBuilder, array $sorting = []): void
     {
         foreach ($sorting as $property => $order) {
-            if (!in_array($property, array_merge($this->_class->getAssociationNames(), $this->_class->getFieldNames()), true)) {
+            if (!in_array($property, array_merge($this->getClassMetadata()->getAssociationNames(), $this->getClassMetadata()->getFieldNames()), true)) {
                 continue;
             }
 
diff --git a/src/Bundle/EventListener/ORMMappedSuperClassSubscriber.php b/src/Bundle/EventListener/ORMMappedSuperClassSubscriber.php
index 11a434613..81d9afac5 100644
--- a/src/Bundle/EventListener/ORMMappedSuperClassSubscriber.php
+++ b/src/Bundle/EventListener/ORMMappedSuperClassSubscriber.php
@@ -82,6 +82,7 @@ private function setAssociationMappings(ClassMetadata $metadata, Configuration $
             if ($parentMetadata->isMappedSuperclass) {
                 foreach ($parentMetadata->getAssociationMappings() as $key => $value) {
                     if ($this->isRelation($value['type']) && !isset($metadata->associationMappings[$key])) {
+                        /** @psalm-suppress PropertyTypeCoercion */
                         $metadata->associationMappings[$key] = $value;
                     }
                 }
diff --git a/src/Bundle/EventListener/ORMTranslatableListener.php b/src/Bundle/EventListener/ORMTranslatableListener.php
index b64e43867..1022862b3 100644
--- a/src/Bundle/EventListener/ORMTranslatableListener.php
+++ b/src/Bundle/EventListener/ORMTranslatableListener.php
@@ -58,6 +58,10 @@ public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs): void
         $classMetadata = $eventArgs->getClassMetadata();
         $reflection = $classMetadata->getReflectionClass();
 
+        if (null === $reflection) {
+            return;
+        }
+
         if ($reflection->isAbstract()) {
             return;
         }
@@ -109,7 +113,7 @@ private function mapTranslatable(ClassMetadata $metadata): void
                 'mappedBy' => 'translatable',
                 'fetch' => ClassMetadata::FETCH_EXTRA_LAZY,
                 'indexBy' => 'locale',
-                'cascade' => ['persist', 'merge', 'remove'],
+                'cascade' => ['persist', 'remove'],
                 'orphanRemoval' => true,
             ]);
         }
diff --git a/src/Component/composer.json b/src/Component/composer.json
index 800ef515e..4cc4d95f5 100644
--- a/src/Component/composer.json
+++ b/src/Component/composer.json
@@ -48,7 +48,7 @@
     },
     "require-dev": {
         "behat/transliterator": "^1.3",
-        "doctrine/orm": "^2.18",
+        "doctrine/orm": "^2.18 || ^3.3",
         "matthiasnoback/symfony-dependency-injection-test": "^4.2.1 || ^5.1",
         "phpspec/phpspec": "^7.3",
         "phpspec/prophecy-phpunit": "^2.0",
@@ -59,7 +59,7 @@
         "twig/twig": "^3.0"
     },
     "conflict": {
-        "doctrine/orm": "<2.18 || ^3.0",
+        "doctrine/orm": "<2.18",
         "twig/twig": "<3.0"
     },
     "extra": {
diff --git a/tests/Application/src/Entity/GedmoBaseExample.php b/tests/Application/src/Entity/GedmoBaseExample.php
index 224b3a3b3..29eb16a81 100644
--- a/tests/Application/src/Entity/GedmoBaseExample.php
+++ b/tests/Application/src/Entity/GedmoBaseExample.php
@@ -17,9 +17,7 @@
 use Gedmo\Mapping\Annotation as Gedmo;
 use Sylius\Resource\Model\ResourceInterface;
 
-#[ORM\Entity]
 #[ORM\MappedSuperclass]
-#[ORM\Table(name: 'gedmo')]
 class GedmoBaseExample implements ResourceInterface
 {
     #[ORM\Id]
diff --git a/tests/Application/src/Entity/GedmoExtendedExample.php b/tests/Application/src/Entity/GedmoExtendedExample.php
index 265fce55b..4461e25af 100644
--- a/tests/Application/src/Entity/GedmoExtendedExample.php
+++ b/tests/Application/src/Entity/GedmoExtendedExample.php
@@ -16,8 +16,6 @@
 use Doctrine\ORM\Mapping as ORM;
 
 #[ORM\Entity]
-#[ORM\MappedSuperclass]
-
 class GedmoExtendedExample extends GedmoBaseExample
 {
     #[ORM\Column(length: 255)]