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

Basic implementation of psr6 compatibility pass #690

Merged
merged 13 commits into from
Oct 16, 2021
100 changes: 100 additions & 0 deletions DependencyInjection/Compiler/CacheCompatibilityPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler;

use Doctrine\Bundle\MongoDBBundle\DependencyInjection\DoctrineMongoDBExtension;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

use function array_keys;
use function is_a;
use function trigger_deprecation;

/** @internal */
final class CacheCompatibilityPass implements CompilerPassInterface
{
private const CACHE_SETTER_METHODS_PSR6_SUPPORT = ['setMetadataCache' => true];

public function process(ContainerBuilder $container): void
{
foreach (array_keys($container->findTaggedServiceIds(DoctrineMongoDBExtension::CONFIGURATION_TAG)) as $id) {
/** @var array{0: string, 1: mixed[]} $methodCall */
foreach ($container->getDefinition($id)->getMethodCalls() as $methodCall) {
Ahummeling marked this conversation as resolved.
Show resolved Hide resolved
$methodName = $methodCall[0];

if (! isset(self::CACHE_SETTER_METHODS_PSR6_SUPPORT[$methodName])) {
continue;
}

$methodArgs = $methodCall[1];
$definitionId = (string) $methodArgs[0];
$aliasId = null;
if ($container->hasAlias($definitionId)) {
Ahummeling marked this conversation as resolved.
Show resolved Hide resolved
$aliasId = $definitionId;
$definitionId = (string) $container->getAlias($aliasId);
}

$shouldBePsr6 = self::CACHE_SETTER_METHODS_PSR6_SUPPORT[$methodName];

$this->wrapIfNecessary($container, $aliasId, $definitionId, $shouldBePsr6);
}
}
}

private function createCompatibilityLayerDefinition(ContainerBuilder $container, string $definitionId, bool $shouldBePsr6): ?Definition
{
$definition = $container->getDefinition($definitionId);

while (! $definition->getClass() && $definition instanceof ChildDefinition) {
$definition = $container->findDefinition($definition->getParent());
}

if ($shouldBePsr6 === is_a($definition->getClass(), CacheItemPoolInterface::class, true)) {
return null;
}

$targetClass = CacheProvider::class;
$targetFactory = DoctrineProvider::class;

if ($shouldBePsr6) {
$targetClass = CacheItemPoolInterface::class;
$targetFactory = CacheAdapter::class;

trigger_deprecation(
'doctrine/mongodb-odm-bundle',
'4.4',
'Configuring doctrine/cache is deprecated. Please update the cache service "%s" to use a PSR-6 cache.',
$definitionId
);
}

return (new Definition($targetClass))
->setFactory([$targetFactory, 'wrap'])
->addArgument(new Reference($definitionId));
}

private function wrapIfNecessary(ContainerBuilder $container, ?string $aliasId, string $definitionId, bool $shouldBePsr6): void
{
$compatibilityLayer = $this->createCompatibilityLayerDefinition($container, $definitionId, $shouldBePsr6);

if ($compatibilityLayer === null) {
return;
}

$aliasId = $aliasId ?? $definitionId;

$compatibilityLayerId = $definitionId . '.compatibility_layer';
$container->setDefinition($compatibilityLayerId, $compatibilityLayer);

$container->setAlias($aliasId, $compatibilityLayerId);
}
}
98 changes: 97 additions & 1 deletion DependencyInjection/DoctrineMongoDBExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
use Doctrine\Bundle\MongoDBBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\Bundle\MongoDBBundle\Fixture\ODMFixtureInterface;
use Doctrine\Bundle\MongoDBBundle\Repository\ServiceDocumentRepositoryInterface;
use Doctrine\Common\Cache\MemcacheCache;
use Doctrine\Common\Cache\RedisCache;
use Doctrine\Common\DataFixtures\Loader as DataFixturesLoader;
use Doctrine\Common\EventSubscriber;
use Doctrine\ODM\MongoDB\DocumentManager;
use InvalidArgumentException;
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
use Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Alias;
Expand All @@ -39,6 +46,9 @@
*/
class DoctrineMongoDBExtension extends AbstractDoctrineExtension
{
/** @internal */
public const CONFIGURATION_TAG = 'doctrine.odm.configuration';
Ahummeling marked this conversation as resolved.
Show resolved Hide resolved

/**
* Responds to the doctrine_mongodb configuration parameter.
*/
Expand Down Expand Up @@ -198,6 +208,7 @@ protected function loadDocumentManager(array $documentManager, $defaultDM, $defa
$defaultDatabase = $documentManager['database'] ?? $defaultDB;

$odmConfigDef = new Definition('%doctrine_mongodb.odm.configuration.class%');
$odmConfigDef->addTag(self::CONFIGURATION_TAG);
$container->setDefinition(
$configurationId,
$odmConfigDef
Expand All @@ -207,7 +218,7 @@ protected function loadDocumentManager(array $documentManager, $defaultDM, $defa
$this->loadObjectManagerCacheDriver($documentManager, $container, 'metadata_cache');

$methods = [
'setMetadataCacheImpl' => new Reference(sprintf('doctrine_mongodb.odm.%s_metadata_cache', $documentManager['name'])),
'setMetadataCache' => new Reference(sprintf('doctrine_mongodb.odm.%s_metadata_cache', $documentManager['name'])),
'setMetadataDriverImpl' => new Reference(sprintf('doctrine_mongodb.odm.%s_metadata_driver', $documentManager['name'])),
'setProxyDir' => '%doctrine_mongodb.odm.proxy_dir%',
'setProxyNamespace' => '%doctrine_mongodb.odm.proxy_namespace%',
Expand Down Expand Up @@ -508,6 +519,91 @@ public function getXsdValidationBasePath()
return __DIR__ . '/../Resources/config/schema';
}

/**
* Loads a cache driver.
*
* @param string $cacheName The cache driver name
* @param string $objectManagerName The object manager name
* @param array $cacheDriver The cache driver mapping
*
* @return string
*
* @throws InvalidArgumentException
*
* @psalm-suppress UndefinedClass this won't be necessary when removing metadata cache configuration
*/
protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheDriver, ContainerBuilder $container)
{
if (isset($cacheDriver['namespace'])) {
return parent::loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
}

$cacheDriverServiceId = $this->getObjectManagerElementName($objectManagerName . '_' . $cacheName);

switch ($cacheDriver['type']) {
case 'service':
$container->setAlias($cacheDriverServiceId, new Alias($cacheDriver['id'], false));

return $cacheDriverServiceId;

case 'memcached':
if (! empty($cacheDriver['class']) && $cacheDriver['class'] !== MemcacheCache::class) {
return parent::loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
}

$memcachedInstanceClass = ! empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%' . $this->getObjectManagerElementName('cache.memcached_instance.class') . '%';
$memcachedHost = ! empty($cacheDriver['host']) ? $cacheDriver['host'] : '%' . $this->getObjectManagerElementName('cache.memcached_host') . '%';
$memcachedPort = ! empty($cacheDriver['port']) ? $cacheDriver['port'] : '%' . $this->getObjectManagerElementName('cache.memcached_port') . '%';
$memcachedInstance = new Definition($memcachedInstanceClass);
$memcachedInstance->addMethodCall('addServer', [
$memcachedHost,
$memcachedPort,
]);
$container->setDefinition($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)), $memcachedInstance);

$cacheDef = new Definition(MemcachedAdapter::class, [new Reference($this->getObjectManagerElementName(sprintf('%s_memcached_instance', $objectManagerName)))]);

break;

case 'redis':
if (! empty($cacheDriver['class']) && $cacheDriver['class'] !== RedisCache::class) {
return parent::loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
}

$redisInstanceClass = ! empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%' . $this->getObjectManagerElementName('cache.redis_instance.class') . '%';
$redisHost = ! empty($cacheDriver['host']) ? $cacheDriver['host'] : '%' . $this->getObjectManagerElementName('cache.redis_host') . '%';
$redisPort = ! empty($cacheDriver['port']) ? $cacheDriver['port'] : '%' . $this->getObjectManagerElementName('cache.redis_port') . '%';
$redisInstance = new Definition($redisInstanceClass);
$redisInstance->addMethodCall('connect', [
$redisHost,
$redisPort,
]);
$container->setDefinition($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)), $redisInstance);

$cacheDef = new Definition(RedisAdapter::class, [new Reference($this->getObjectManagerElementName(sprintf('%s_redis_instance', $objectManagerName)))]);

break;

case 'apcu':
$cacheDef = new Definition(ApcuAdapter::class);

break;

case 'array':
$cacheDef = new Definition(ArrayAdapter::class);

break;

default:
return parent::loadCacheDriver($cacheName, $objectManagerName, $cacheDriver, $container);
}

$cacheDef->setPublic(false);
$container->setDefinition($cacheDriverServiceId, $cacheDef);

return $cacheDriverServiceId;
}

private function buildDeprecationArgs(string $version, string $message): array
{
// @todo Remove when support for Symfony 5.1 and older is dropped
Expand Down
2 changes: 2 additions & 0 deletions DoctrineMongoDBBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\Bundle\MongoDBBundle;

use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\CacheCompatibilityPass;
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\CreateHydratorDirectoryPass;
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\CreateProxyDirectoryPass;
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\FixturesCompilerPass;
Expand Down Expand Up @@ -33,6 +34,7 @@ class DoctrineMongoDBBundle extends Bundle

public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CacheCompatibilityPass());
$container->addCompilerPass(new RegisterEventListenersAndSubscribersPass('doctrine_mongodb.odm.connections', 'doctrine_mongodb.odm.%s_connection.event_manager', 'doctrine_mongodb.odm'), PassConfig::TYPE_BEFORE_OPTIMIZATION);
$container->addCompilerPass(new CreateProxyDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new CreateHydratorDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING);
Expand Down
17 changes: 17 additions & 0 deletions Repository/ContainerRepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Doctrine\ODM\MongoDB\Repository\GridFSRepository;
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
use Doctrine\Persistence\ObjectRepository;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -37,6 +38,13 @@ public function __construct(ContainerInterface $container)
$this->container = $container;
}

/**
* @psalm-param class-string<T> $documentName
*
* @psalm-return ObjectRepository<T>
*
* @template T of object
*/
public function getRepository(DocumentManager $documentManager, string $documentName): ObjectRepository
{
$metadata = $documentManager->getClassMetadata($documentName);
Expand All @@ -45,6 +53,7 @@ public function getRepository(DocumentManager $documentManager, string $document
if ($customRepositoryName !== null) {
// fetch from the container
if ($this->container && $this->container->has($customRepositoryName)) {
/** @var ObjectRepository<T> $repository */
$repository = $this->container->get($customRepositoryName);

if (! $repository instanceof DocumentRepository) {
Expand All @@ -69,6 +78,13 @@ public function getRepository(DocumentManager $documentManager, string $document
return $this->getOrCreateRepository($documentManager, $metadata);
}

/**
* @psalm-param ClassMetadata<T> $metadata
*
* @psalm-return ObjectRepository<T>
*
* @template T of object
*/
private function getOrCreateRepository(DocumentManager $documentManager, ClassMetadata $metadata): ObjectRepository
{
$repositoryHash = $metadata->getName() . spl_object_hash($documentManager);
Expand All @@ -79,6 +95,7 @@ private function getOrCreateRepository(DocumentManager $documentManager, ClassMe
if ($metadata->customRepositoryClassName) {
$repositoryClassName = $metadata->customRepositoryClassName;
} elseif ($metadata->isFile) {
/** @psalm-var class-string<GridFSRepository<T>> $repositoryClassName */
$repositoryClassName = $documentManager->getConfiguration()->getDefaultGridFSRepositoryClassName();
} else {
$repositoryClassName = $documentManager->getConfiguration()->getDefaultDocumentRepositoryClassName();
Expand Down
4 changes: 4 additions & 0 deletions Repository/ServiceDocumentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
* parent::__construct($registry, YourDocument::class);
* }
* }
*
* @template T of object
* @template-extends DocumentRepository<T>
*/
class ServiceDocumentRepository extends DocumentRepository implements ServiceDocumentRepositoryInterface
{
/** @use ServiceRepositoryTrait<T> */
use ServiceRepositoryTrait;
}
4 changes: 4 additions & 0 deletions Repository/ServiceRepositoryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
use function assert;
use function sprintf;

/**
* @template T of object
*/
trait ServiceRepositoryTrait
{
/**
* @param string $documentClass The class name of the entity this repository manages
* @psalm-param class-string<T> $documentClass
*/
public function __construct(ManagerRegistry $registry, $documentClass)
{
Expand Down
6 changes: 6 additions & 0 deletions Tests/DependencyInjection/AbstractMongoDBExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
{
abstract protected function loadFromFile(ContainerBuilder $container, string $file): void;

/**
* @psalm-suppress UndefinedClass this won't be necessary when removing metadata cache configuration
*/
public function testDependencyInjectionConfigurationDefaults(): void
{
$container = $this->getContainer();
Expand Down Expand Up @@ -336,6 +339,9 @@ public function testDocumentManagerMetadataCacheDriverConfiguration(): void
$this->assertEquals('%doctrine_mongodb.odm.cache.apc.class%', $definition->getClass());
}

/**
* @psalm-suppress UndefinedClass this won't be necessary when removing metadata cache configuration
*/
public function testDocumentManagerMemcachedMetadataCacheDriverConfiguration(): void
{
$container = $this->getContainer();
Expand Down
Loading