From 34ba42fff9f5226d5a766bade6103d903e609005 Mon Sep 17 00:00:00 2001 From: Alexander Rakushin Date: Sat, 20 Jul 2024 12:09:51 +0300 Subject: [PATCH 1/2] Added the "forceUseAttributeReader" option, which allows you to use AttributeReader auto --- CHANGELOG.md | 1 + src/Mapping/ExtensionMetadataFactory.php | 22 +++++++++++++++++++--- src/Mapping/MappedEventSubscriber.php | 13 ++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bee883f90b..0c1d417956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ a release. ### Added - IP address provider for use with extensions with IP address references (#2928) +- Mapping Driver: Added option `forceUseAttributeReader`, force the use of AttributeReader for Gedmo attributes ( ignore default XML driver for all namespaces) (#2613) ## [3.19.0] - 2025-02-24 ### Added diff --git a/src/Mapping/ExtensionMetadataFactory.php b/src/Mapping/ExtensionMetadataFactory.php index 16d321cc3f..5dc67737ca 100644 --- a/src/Mapping/ExtensionMetadataFactory.php +++ b/src/Mapping/ExtensionMetadataFactory.php @@ -15,6 +15,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as DocumentClassMetadata; use Doctrine\ORM\Mapping\ClassMetadata as EntityClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo as LegacyEntityClassMetadata; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Mapping\Driver\DefaultFileLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; @@ -70,12 +71,18 @@ class ExtensionMetadataFactory private ?CacheItemPoolInterface $cacheItemPool = null; + /** + * Ignore doctrine driver class and force use attribute reader for gedmo properties + * @var bool + */ + private $forceUseAttributeReader; + /** * @param Reader|AttributeReader|object|null $annotationReader * * @note Providing any object as the third argument is deprecated, as of 4.0 an {@see AttributeReader} will be required */ - public function __construct(ObjectManager $objectManager, string $extensionNamespace, ?object $annotationReader = null, ?CacheItemPoolInterface $cacheItemPool = null) + public function __construct(ObjectManager $objectManager, string $extensionNamespace, ?object $annotationReader = null, ?CacheItemPoolInterface $cacheItemPool = null, bool $forceUseAttributeReader = false) { if (null !== $annotationReader) { if ($annotationReader instanceof Reader) { @@ -101,6 +108,7 @@ public function __construct(ObjectManager $objectManager, string $extensionNames $this->objectManager = $objectManager; $this->annotationReader = $annotationReader; $this->extensionNamespace = $extensionNamespace; + $this->forceUseAttributeReader = $forceUseAttributeReader; $omDriver = $objectManager->getConfiguration()->getMetadataDriverImpl(); $this->driver = $this->getDriver($omDriver); $this->cacheItemPool = $cacheItemPool; @@ -205,11 +213,19 @@ protected function getDriver($omDriver) $driverName = substr($className, strrpos($className, '\\') + 1); if ($omDriver instanceof MappingDriverChain || 'DriverChain' === $driverName) { $driver = new Chain(); + $attributeDriver = $this->forceUseAttributeReader ? new AttributeDriver([], true) : null; foreach ($omDriver->getDrivers() as $namespace => $nestedOmDriver) { + if (!$nestedOmDriver instanceof AttributeDriver && $attributeDriver) { + $nestedOmDriver = $attributeDriver; + } $driver->addDriver($this->getDriver($nestedOmDriver), $namespace); } - if (null !== $omDriver->getDefaultDriver()) { - $driver->setDefaultDriver($this->getDriver($omDriver->getDefaultDriver())); + if ($attributeDriver || null !== $omDriver->getDefaultDriver()) { + $defDriver = $omDriver->getDefaultDriver(); + if (!$defDriver instanceof AttributeDriver && $attributeDriver) { + $defDriver = $attributeDriver; + } + $driver->setDefaultDriver($this->getDriver($defDriver)); } } else { $driverName = substr($driverName, 0, strpos($driverName, 'Driver')); diff --git a/src/Mapping/MappedEventSubscriber.php b/src/Mapping/MappedEventSubscriber.php index 640e5cb984..ad1c91898f 100644 --- a/src/Mapping/MappedEventSubscriber.php +++ b/src/Mapping/MappedEventSubscriber.php @@ -99,12 +99,22 @@ abstract class MappedEventSubscriber implements EventSubscriber private ?ClockInterface $clock = null; + /** + * Ignore doctrine driver class and force use attribute reader for gedmo properties + * @var bool + */ + private $forceUseAttributeReader = false; + public function __construct() { $parts = explode('\\', $this->getNamespace()); $this->name = end($parts); } + public function setForceUseAttributeReader(bool $forceUseAttributeReader) { + $this->forceUseAttributeReader = $forceUseAttributeReader; + } + /** * Get the configuration for specific object class * if cache driver is present it scans it also @@ -166,7 +176,8 @@ public function getExtensionMetadataFactory(ObjectManager $objectManager) $objectManager, $this->getNamespace(), $this->annotationReader, - $this->getCacheItemPool($objectManager) + $this->getCacheItemPool($objectManager), + $this->forceUseAttributeReader ); } From f439e38ee1a3154c6b1bf8c4018503f512becb2c Mon Sep 17 00:00:00 2001 From: Alexander Rakushin Date: Wed, 1 Oct 2025 14:03:40 +0300 Subject: [PATCH 2/2] Add test for forceUseAttributeReader option --- ...ixture.Xml.SeparateDoctrineMapping.dcm.xml | 11 ++ .../Fixture/Xml/SeparateDoctrineMapping.php | 34 +++++ .../SeparateWithDoctrineDriverMappingTest.php | 135 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 tests/Gedmo/Mapping/Driver/Xml/Gedmo.Tests.Mapping.Fixture.Xml.SeparateDoctrineMapping.dcm.xml create mode 100644 tests/Gedmo/Mapping/Fixture/Xml/SeparateDoctrineMapping.php create mode 100644 tests/Gedmo/Mapping/SeparateWithDoctrineDriverMappingTest.php diff --git a/tests/Gedmo/Mapping/Driver/Xml/Gedmo.Tests.Mapping.Fixture.Xml.SeparateDoctrineMapping.dcm.xml b/tests/Gedmo/Mapping/Driver/Xml/Gedmo.Tests.Mapping.Fixture.Xml.SeparateDoctrineMapping.dcm.xml new file mode 100644 index 0000000000..d72d9e0bdb --- /dev/null +++ b/tests/Gedmo/Mapping/Driver/Xml/Gedmo.Tests.Mapping.Fixture.Xml.SeparateDoctrineMapping.dcm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/Gedmo/Mapping/Fixture/Xml/SeparateDoctrineMapping.php b/tests/Gedmo/Mapping/Fixture/Xml/SeparateDoctrineMapping.php new file mode 100644 index 0000000000..9f46358490 --- /dev/null +++ b/tests/Gedmo/Mapping/Fixture/Xml/SeparateDoctrineMapping.php @@ -0,0 +1,34 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Tests\Mapping\Fixture\Xml; +use Gedmo\Mapping\Annotation as Gedmo; + +class SeparateDoctrineMapping +{ + /** + * @var int + */ + private $id; + + private $title; + + /** + * @var \DateTime + */ + #[Gedmo\Timestampable(on: 'create')] + private $created; + /** + * @var \DateTime + */ + #[Gedmo\Timestampable(on: 'update')] + private $updated; +} diff --git a/tests/Gedmo/Mapping/SeparateWithDoctrineDriverMappingTest.php b/tests/Gedmo/Mapping/SeparateWithDoctrineDriverMappingTest.php new file mode 100644 index 0000000000..9de2ffa345 --- /dev/null +++ b/tests/Gedmo/Mapping/SeparateWithDoctrineDriverMappingTest.php @@ -0,0 +1,135 @@ + http://www.gediminasm.org + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Gedmo\Mapping; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Doctrine\ORM\Mapping\Driver\YamlDriver; +use Gedmo\Tests\Mapping\Fixture\Category; +use Gedmo\Tests\Mapping\Fixture\Xml\SeparateDoctrineMapping; +use Gedmo\Tests\Mapping\Fixture\Xml\Timestampable; +use Gedmo\Tests\Mapping\ORMMappingTestCase; +use Gedmo\Timestampable\TimestampableListener; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +/** + * These are mapping tests for timestampable extension + * + * @author Gediminas Morkevicius + */ +final class SeparateWithDoctrineDriverMappingTest extends ORMMappingTestCase +{ + private EntityManager $em; + private EntityManager $emWithForceAttr; + /** + * @var CacheItemPoolInterface + */ + protected $cacheWithForceAttr; + + protected function setUp(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.'); + } + + parent::setUp(); + + // Base Configuration without forceUseAttributeReader option + $listener = new TimestampableListener(); + $listener->setCacheItemPool($this->cache); + + $this->em = $this->getBasicEntityManager(); + $this->em->getEventManager()->addEventSubscriber($listener); + + + // Configuration with forceUseAttributeReader option + $this->cacheWithForceAttr = new ArrayAdapter(); + $listener = new TimestampableListener(); + $listener->setCacheItemPool($this->cacheWithForceAttr); + $listener->setForceUseAttributeReader(true); + $this->emWithForceAttr = $this->getBasicEntityManager(); + $this->emWithForceAttr->getEventManager()->addEventSubscriber($listener); + + + } + + + /** + * @return \Generator + * + * @note the XML fixture has a different mapping from the other configs, so it is tested separately + */ + public static function dataTimestampableObject(): \Generator + { + yield 'Model with separated doctrine(XML Driver), gedmo(Attribute Driver)' => [SeparateDoctrineMapping::class, false, true]; + yield 'Model without attributes (XML Driver only)' => [Timestampable::class, false, false]; + yield 'Model with attributes (Attribute Driver only)' => [Category::class, true, true]; + + } + + /** + * @param class-string $className + * + * @dataProvider dataTimestampableObject + */ + public function testForceGedmoInAttributeDriverMapping(string $className, bool $doctrineMappingInAttributes, bool $gedmoMappingInAttributes): void + { + // This entityManager configured to enforce the use of attributes for gedmo. + $em = $this->emWithForceAttr; + $cache = $this->cacheWithForceAttr; + $em->getClassMetadata($className); + + $this->em->getClassMetadata($className); + $cacheId = ExtensionMetadataFactory::getCacheId($className, 'Gedmo\Timestampable'); + $config = $cache->getItem($cacheId)->get(); + + if($gedmoMappingInAttributes) { + static::assertArrayHasKey('create', $config); + static::assertSame('created', $config['create'][0]); + static::assertArrayHasKey('update', $config); + static::assertSame('updated', $config['update'][0]); + }else{ + static::assertArrayNotHasKey('create', $config); + static::assertArrayNotHasKey('update', $config); + } + } + /** + * @param class-string $className + * + * @dataProvider dataTimestampableObject + */ + public function testStandardWayMapping(string $className, bool $doctrineMappingInAttributes, bool $gedmoMappingInAttributes): void + { + $em = $this->em; + $cache = $this->cache; + $em->getClassMetadata($className); + + $this->em->getClassMetadata($className); + $cacheId = ExtensionMetadataFactory::getCacheId($className, 'Gedmo\Timestampable'); + $config = $cache->getItem($cacheId)->get(); + + if($gedmoMappingInAttributes && !$doctrineMappingInAttributes) { + // example: doctrine use XmlDriver, Gedmo - use AttributeDriver, + // Gedmo attributes - unreadable + static::assertArrayNotHasKey('create', $config); + static::assertArrayNotHasKey('update', $config); + }else{ + static::assertArrayHasKey('create', $config); + static::assertSame('created', $config['create'][0]); + static::assertArrayHasKey('update', $config); + static::assertSame('updated', $config['update'][0]); + } + + } + +}