Skip to content
Open
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 19 additions & 3 deletions src/Mapping/ExtensionMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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'));
Expand Down
13 changes: 12 additions & 1 deletion src/Mapping/MappedEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -166,7 +176,8 @@ public function getExtensionMetadataFactory(ObjectManager $objectManager)
$objectManager,
$this->getNamespace(),
$this->annotationReader,
$this->getCacheItemPool($objectManager)
$this->getCacheItemPool($objectManager),
$this->forceUseAttributeReader
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" >
<entity name="Gedmo\Tests\Mapping\Fixture\Xml\SeparateDoctrineMapping" table="separate_doctrine_mapping">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="title" type="string"/>
<field name="created" type="datetime"/>
<field name="updated" type="datetime"/>
</entity>
</doctrine-mapping>
34 changes: 34 additions & 0 deletions tests/Gedmo/Mapping/Fixture/Xml/SeparateDoctrineMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Doctrine Behavioral Extensions package.
* (c) Gediminas Morkevicius <[email protected]> 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;
}
135 changes: 135 additions & 0 deletions tests/Gedmo/Mapping/SeparateWithDoctrineDriverMappingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Doctrine Behavioral Extensions package.
* (c) Gediminas Morkevicius <[email protected]> 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 <[email protected]>
*/
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<string, array{class-string}>
*
* @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]);
}

}

}