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

Allow to set a cache to store configuration #2373

Merged
merged 1 commit into from
Dec 31, 2021
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ a release.
- References: Avoid deprecations using LazyCollection with PHP 8.1
- Tree: Association mapping problems using Closure tree strategy (by manually defining mapping on the closure entity).
- Wrong PHPDoc type declarations.
- Avoid calling deprecated `AbstractClassMetadataFactory::getCacheDriver()` method.

### Deprecated
- Tree: When using Closure tree strategy, it is deprecated not defining the mapping associations of the closure entity.

### Changed
- In order to use a custom cache for storing configuration of an extension, the user has to call `setCacheItemPool()`
on the extension listener passing an instance of `Psr\Cache\CacheItemPoolInterface`.

## [3.4.0] - 2021-12-05
### Added
- PHP 8 Attributes support for Doctrine MongoDB to document & traits.
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"doctrine/collections": "^1.0",
"doctrine/common": "^2.13 || ^3.0",
"doctrine/event-manager": "^1.0",
"doctrine/persistence": "^1.3.3 || ^2.0"
"doctrine/persistence": "^1.3.3 || ^2.0",
"psr/cache": "^1 || ^2 || ^3",
"symfony/cache": "^4.4 || ^5.3 || ^6.0"
},
"require-dev": {
"doctrine/cache": "^1.11 || ^2.0",
Expand All @@ -58,7 +60,6 @@
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/cache": "^4.4 || ^5.3 || ^6.0",
"symfony/console": "^4.4 || ^5.3 || ^6.0",
"symfony/yaml": "^4.4 || ^5.3 || ^6.0"
},
Expand Down
7 changes: 7 additions & 0 deletions example/em.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,32 @@
// Sluggable extension
$sluggableListener = new Gedmo\Sluggable\SluggableListener();
$sluggableListener->setAnnotationReader($annotationReader);
$sluggableListener->setCacheItemPool($cache);
$eventManager->addEventSubscriber($sluggableListener);

// Tree extension
$treeListener = new Gedmo\Tree\TreeListener();
$treeListener->setAnnotationReader($annotationReader);
$treeListener->setCacheItemPool($cache);
$eventManager->addEventSubscriber($treeListener);

// Loggable extension, not used in example
//$loggableListener = new Gedmo\Loggable\LoggableListener;
//$loggableListener->setAnnotationReader($annotationReader);
//$loggableListener->setCacheItemPool($cache);
//$loggableListener->setUsername('admin');
//$eventManager->addEventSubscriber($loggableListener);

// Timestampable extension
$timestampableListener = new Gedmo\Timestampable\TimestampableListener();
$timestampableListener->setAnnotationReader($annotationReader);
$timestampableListener->setCacheItemPool($cache);
$eventManager->addEventSubscriber($timestampableListener);

// Blameable extension
$blameableListener = new \Gedmo\Blameable\BlameableListener();
$blameableListener->setAnnotationReader($annotationReader);
$blameableListener->setCacheItemPool($cache);
$blameableListener->setUserValue('MyUsername'); // determine from your environment
$eventManager->addEventSubscriber($blameableListener);

Expand All @@ -111,11 +116,13 @@
$translatableListener->setTranslatableLocale('en');
$translatableListener->setDefaultLocale('en');
$translatableListener->setAnnotationReader($annotationReader);
$translatableListener->setCacheItemPool($cache);
$eventManager->addEventSubscriber($translatableListener);

// Sortable extension, not used in example
//$sortableListener = new Gedmo\Sortable\SortableListener;
//$sortableListener->setAnnotationReader($annotationReader);
//$sortableListener->setCacheItemPool($cache);
//$eventManager->addEventSubscriber($sortableListener);

// Now we will build our ORM configuration.
Expand Down
37 changes: 22 additions & 15 deletions src/Mapping/ExtensionMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace Gedmo\Mapping;

use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver as DoctrineBundleMappingDriver;
use Doctrine\Common\Cache\Cache;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\DefaultFileLocator;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
Expand All @@ -22,6 +21,7 @@
use Gedmo\Mapping\Driver\AttributeDriverInterface;
use Gedmo\Mapping\Driver\AttributeReader;
use Gedmo\Mapping\Driver\File as FileDriver;
use Psr\Cache\CacheItemPoolInterface;

/**
* The extension metadata factory is responsible for extension driver
Expand Down Expand Up @@ -60,18 +60,18 @@ class ExtensionMetadataFactory
protected $annotationReader;

/**
* Initializes extension driver
*
* @param string $extensionNamespace
* @param object $annotationReader
* @var CacheItemPoolInterface|null
*/
public function __construct(ObjectManager $objectManager, $extensionNamespace, $annotationReader)
private $cacheItemPool;

public function __construct(ObjectManager $objectManager, string $extensionNamespace, object $annotationReader, ?CacheItemPoolInterface $cacheItemPool = null)
{
$this->objectManager = $objectManager;
$this->annotationReader = $annotationReader;
$this->extensionNamespace = $extensionNamespace;
$omDriver = $objectManager->getConfiguration()->getMetadataDriverImpl();
$this->driver = $this->getDriver($omDriver);
$this->cacheItemPool = $cacheItemPool;
}

/**
Expand Down Expand Up @@ -111,14 +111,7 @@ public function getExtensionMetadata($meta)
$config['useObjectClass'] = $useObjectName;
}

$cacheDriver = $cmf->getCacheDriver();

if ($cacheDriver instanceof Cache) {
// Cache the result, even if it's empty, to prevent re-parsing non-existent annotations.
$cacheId = self::getCacheId($meta->getName(), $this->extensionNamespace);

$cacheDriver->save($cacheId, $config);
}
$this->storeConfiguration($meta->getName(), $config);

return $config;
}
Expand All @@ -133,7 +126,7 @@ public function getExtensionMetadata($meta)
*/
public static function getCacheId($className, $extensionNamespace)
{
return $className.'\\$'.strtoupper(str_replace('\\', '_', $extensionNamespace)).'_CLASSMETADATA';
return str_replace('\\', '_', $className).'_$'.strtoupper(str_replace('\\', '_', $extensionNamespace)).'_CLASSMETADATA';
}

/**
Expand Down Expand Up @@ -202,4 +195,18 @@ protected function getDriver($omDriver)

return $driver;
}

private function storeConfiguration(string $className, array $config): void
{
if (null === $this->cacheItemPool) {
return;
}

// Cache the result, even if it's empty, to prevent re-parsing non-existent annotations.
$cacheId = self::getCacheId($className, $this->extensionNamespace);

$item = $this->cacheItemPool->getItem($cacheId);

$this->cacheItemPool->save($item->set($config));
}
}
62 changes: 39 additions & 23 deletions src/Mapping/MappedEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\ObjectManager;
use Gedmo\Mapping\Event\AdapterInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

/**
Expand Down Expand Up @@ -80,12 +81,15 @@ abstract class MappedEventSubscriber implements EventSubscriber
private static $defaultAnnotationReader;

/**
* Constructor
* @var CacheItemPoolInterface
*/
private $cacheItemPool;

public function __construct()
{
$parts = explode('\\', $this->getNamespace());
$this->name = end($parts);
$this->cacheItemPool = new ArrayAdapter();
}

/**
Expand All @@ -98,32 +102,33 @@ public function __construct()
*/
public function getConfiguration(ObjectManager $objectManager, $class)
{
$config = [];
if (isset(self::$configurations[$this->name][$class])) {
$config = self::$configurations[$this->name][$class];
} else {
$factory = $objectManager->getMetadataFactory();
$cacheDriver = $factory->getCacheDriver();
if ($cacheDriver) {
$cacheId = ExtensionMetadataFactory::getCacheId($class, $this->getNamespace());
if (false !== ($cached = $cacheDriver->fetch($cacheId))) {
self::$configurations[$this->name][$class] = $cached;
$config = $cached;
} else {
// re-generate metadata on cache miss
$this->loadMetadataForObjectClass($objectManager, $factory->getMetadataFor($class));
if (isset(self::$configurations[$this->name][$class])) {
$config = self::$configurations[$this->name][$class];
}
}
return self::$configurations[$this->name][$class];
}

$objectClass = $config['useObjectClass'] ?? $class;
if ($objectClass !== $class) {
$this->getConfiguration($objectManager, $objectClass);
}
$config = [];

$cacheItemPool = $this->getCacheItemPool();

$cacheId = ExtensionMetadataFactory::getCacheId($class, $this->getNamespace());
$cacheItem = $cacheItemPool->getItem($cacheId);

if ($cacheItem->isHit()) {
$config = $cacheItem->get();
self::$configurations[$this->name][$class] = $config;
} else {
// re-generate metadata on cache miss
$this->loadMetadataForObjectClass($objectManager, $objectManager->getClassMetadata($class));
if (isset(self::$configurations[$this->name][$class])) {
$config = self::$configurations[$this->name][$class];
}
}

$objectClass = $config['useObjectClass'] ?? $class;
if ($objectClass !== $class) {
$this->getConfiguration($objectManager, $objectClass);
}

return $config;
}

Expand All @@ -143,7 +148,8 @@ public function getExtensionMetadataFactory(ObjectManager $objectManager)
$this->extensionMetadataFactory[$oid] = new ExtensionMetadataFactory(
$objectManager,
$this->getNamespace(),
$this->annotationReader
$this->annotationReader,
$this->getCacheItemPool()
);
}

Expand All @@ -168,6 +174,11 @@ public function setAnnotationReader($reader)
$this->annotationReader = $reader;
}

final public function setCacheItemPool(CacheItemPoolInterface $cacheItemPool): void
{
$this->cacheItemPool = $cacheItemPool;
}

/**
* Scans the objects for extended annotations
* event subscribers must subscribe to loadClassMetadata event
Expand Down Expand Up @@ -269,4 +280,9 @@ private function getDefaultAnnotationReader(): Reader

return self::$defaultAnnotationReader;
}

private function getCacheItemPool(): CacheItemPoolInterface
{
return $this->cacheItemPool;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,33 @@

namespace Gedmo\Tests\Mapping;

use Doctrine\Common\EventManager;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Gedmo\Loggable\Entity\LogEntry;
use Gedmo\Loggable\LoggableListener;
use Gedmo\Mapping\ExtensionMetadataFactory;
use Gedmo\Tests\Mapping\Fixture\Yaml\Category;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

/**
* These are mapping tests for tree extension
*
* @author Gediminas Morkevicius <[email protected]>
*/
final class LoggableMappingTest extends \PHPUnit\Framework\TestCase
final class LoggableORMMappingTest extends ORMMappingTestCase
{
public const YAML_CATEGORY = Category::class;

/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;

protected function setUp(): void
{
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCache(new ArrayAdapter());
$config->setQueryCache(new ArrayAdapter());
$config->setProxyDir(TESTS_TEMP_DIR);
$config->setProxyNamespace('Gedmo\Mapping\Proxy');
parent::setUp();

$config = $this->getBasicConfiguration();
$chainDriverImpl = new MappingDriverChain();
$chainDriverImpl->addDriver(
new YamlDriver([__DIR__.'/Driver/Yaml']),
Expand All @@ -48,18 +50,18 @@ protected function setUp(): void
'memory' => true,
];

//$config->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());

$evm = new \Doctrine\Common\EventManager();
$evm->addEventSubscriber(new LoggableListener());
$evm = new EventManager();
$loggableListener = new LoggableListener();
$loggableListener->setCacheItemPool($this->cache);
$evm->addEventSubscriber($loggableListener);
$this->em = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);
}

public function testLoggableMapping(): void
{
$meta = $this->em->getClassMetadata(self::YAML_CATEGORY);
$cacheId = ExtensionMetadataFactory::getCacheId(self::YAML_CATEGORY, 'Gedmo\Loggable');
$config = $this->em->getMetadataFactory()->getCacheDriver()->fetch($cacheId);
$config = $this->cache->getItem($cacheId)->get();

static::assertArrayHasKey('loggable', $config);
static::assertTrue($config['loggable']);
Expand Down
41 changes: 41 additions & 0 deletions tests/Gedmo/Mapping/ORMMappingTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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;

use Doctrine\ORM\Configuration;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

abstract class ORMMappingTestCase extends TestCase
{
/**
* @var CacheItemPoolInterface
*/
protected $cache;

protected function setUp(): void
{
$this->cache = new ArrayAdapter();
}

final protected function getBasicConfiguration(): Configuration
{
$config = new Configuration();
$config->setMetadataCache(new ArrayAdapter());
$config->setQueryCache(new ArrayAdapter());
$config->setProxyDir(TESTS_TEMP_DIR);
$config->setProxyNamespace('Gedmo\Mapping\Proxy');

return $config;
}
}
Loading