Skip to content

Commit

Permalink
Enable @CustomIdGenerator() to reference services tagged as "doctri…
Browse files Browse the repository at this point in the history
…ne.id_generator"
  • Loading branch information
nicolas-grekas committed Jan 30, 2021
1 parent 91f0139 commit 8f92b94
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 0 deletions.
80 changes: 80 additions & 0 deletions DependencyInjection/Compiler/IdGeneratorPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;

use Doctrine\Bundle\DoctrineBundle\Mapping\ClassMetadataFactory;
use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver;
use Doctrine\ORM\Mapping\ClassMetadataFactory as ORMClassMetadataFactory;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class IdGeneratorPass implements CompilerPassInterface
{
const ID_GENERATOR_TAG = 'doctrine.id_generator';
const CONFIGURATION_TAG = 'doctrine.orm.configuration';

public function process(ContainerBuilder $container): void
{
$generatorIds = array_keys($container->findTaggedServiceIds(self::ID_GENERATOR_TAG));
$configurationIds = array_keys($container->findTaggedServiceIds(self::CONFIGURATION_TAG));

foreach ($configurationIds as $id) {
$container->getDefinition($id)->clearTag(self::CONFIGURATION_TAG);
}

// when ORM is not enabled
if (! $container->hasDefinition('doctrine.orm.configuration') || ! $generatorIds) {
return;
}

$generatorRefs = array_map(static function ($id) {
return new Reference($id);
}, $generatorIds);

$ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs));
$container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false));

foreach ($configurationIds as $id) {
$configurationDef = $container->getDefinition($id);
$methodCalls = $configurationDef->getMethodCalls();
$metadataDriverImpl = null;

foreach ($methodCalls as $i => [$method, $arguments]) {
if ($method === 'setMetadataDriverImpl') {
$metadataDriverImpl = (string) $arguments[0];
}

if ($method !== 'setClassMetadataFactoryName') {
continue;
}

if ($arguments[0] !== ORMClassMetadataFactory::class && $arguments[0] !== ClassMetadataFactory::class) {
$class = $container->getReflectionClass($arguments[0]);

if ($class && $class->isSubclassOf(ClassMetadataFactory::class)) {
break;
}

continue 2;
}

$methodCalls[$i] = ['setClassMetadataFactoryName', [ClassMetadataFactory::class]];
}

if (null === $metadataDriverImpl) {
continue;
}

$configurationDef->setMethodCalls($methodCalls);
$container->register('.' . $metadataDriverImpl, MappingDriver::class)
->setDecoratedService($metadataDriverImpl)
->setArguments([
new Reference(sprintf('.%s.inner', $metadataDriverImpl)),
new Reference('doctrine.id_generator_locator'),
]);
}
}
}
16 changes: 16 additions & 0 deletions DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\ImportDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider;
use Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
Expand All @@ -15,10 +16,13 @@
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\Proxy\Autoloader;
use Doctrine\ORM\UnitOfWork;
use LogicException;
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber;
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
Expand Down Expand Up @@ -405,6 +409,14 @@ protected function ormLoad(array $config, ContainerBuilder $container)
$container->removeDefinition('doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber');
}

if (! class_exists(UlidGenerator::class)) {
$container->removeDefinition('doctrine.ulid_generator');
}

if (! class_exists(UuidGenerator::class)) {
$container->removeDefinition('doctrine.uuid_generator');
}

$entityManagers = [];
foreach (array_keys($config['entity_managers']) as $name) {
$entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
Expand Down Expand Up @@ -459,6 +471,9 @@ protected function ormLoad(array $config, ContainerBuilder $container)
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('doctrine.event_subscriber');

$container->registerForAutoconfiguration(AbstractIdGenerator::class)
->addTag(IdGeneratorPass::ID_GENERATOR_TAG);

/**
* @see DoctrineBundle::boot()
*/
Expand All @@ -477,6 +492,7 @@ protected function ormLoad(array $config, ContainerBuilder $container)
protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
{
$ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
$ormConfigDef->addTag(IdGeneratorPass::CONFIGURATION_TAG);

$this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
$this->loadOrmCacheDrivers($entityManager, $container);
Expand Down
2 changes: 2 additions & 0 deletions DoctrineBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheSchemaSubscriberPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass;
use Doctrine\Common\Util\ClassUtils;
Expand Down Expand Up @@ -42,6 +43,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new DoctrineValidationPass('orm'));
$container->addCompilerPass(new EntityListenerPass());
$container->addCompilerPass(new ServiceRepositoryCompilerPass());
$container->addCompilerPass(new IdGeneratorPass());
$container->addCompilerPass(new WellKnownSchemaFilterPass());
$container->addCompilerPass(new DbalSchemaFilterPass());
$container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10);
Expand Down
30 changes: 30 additions & 0 deletions Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory as BaseClassMetadataFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface;

class ClassMetadataFactory extends BaseClassMetadataFactory
{
/**
* {@inheritDoc}
*/
protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents): void
{
parent::doLoadMetadata($class, $parent, $rootEntityFound, $nonSuperclassParents);

$customGeneratorDefinition = $class->customGeneratorDefinition;

if (! isset($customGeneratorDefinition['instance'])) {
return;
}

$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM);
$class->setIdGenerator($customGeneratorDefinition['instance']);
unset($customGeneratorDefinition['instance']);
$class->setCustomGeneratorDefinition($customGeneratorDefinition);
}
}
59 changes: 59 additions & 0 deletions Mapping/MappingDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface;
use Psr\Container\ContainerInterface;

class MappingDriver implements MappingDriverInterface
{
/** @var MappingDriverInterface */
private $driver;

/** @var ContainerInterface */
private $idGeneratorLocator;

public function __construct(MappingDriverInterface $driver, ContainerInterface $idGeneratorLocator)
{
$this->driver = $driver;
$this->idGeneratorLocator = $idGeneratorLocator;
}

/**
* {@inheritDoc}
*/
public function getAllClassNames()
{
return $this->driver->getAllClassNames();
}

/**
* {@inheritDoc}
*/
public function isTransient($className): bool
{
return $this->driver->isTransient($className);
}

/**
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata): void
{
$this->driver->loadMetadataForClass($className, $metadata);

if (
$metadata->generatorType !== ClassMetadataInfo::GENERATOR_TYPE_CUSTOM
|| ! isset($metadata->customGeneratorDefinition['class'])
|| ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class'])
) {
return;
}

$idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']);
$metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition);
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
}
}
13 changes: 13 additions & 0 deletions Resources/config/orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -213,5 +213,18 @@

<tag name="console.command" command="doctrine:mapping:import" />
</service>

<service id="doctrine.ulid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator" public="false">
<argument type="service" id="doctrine.ulid_factory" on-invalid="ignore" />
<tag name="doctrine.id_generator" />
</service>

<service id="doctrine.uuid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator" public="false">
<argument type="service" id="doctrine.uuid_factory" on-invalid="ignore" />
<tag name="doctrine.id_generator" />
</service>

<service id="doctrine.ulid_factory" alias="ulid.factory" public="false" />
<service id="doctrine.uuid_factory" alias="time_based_uuid.factory" public="false" />
</services>
</container>
90 changes: 90 additions & 0 deletions Tests/CustomIdGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Tests;

use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension;
use Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures\CustomIdGenerator;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\EntityManagerInterface;
use Fixtures\Bundles\AnnotationsBundle\AnnotationsBundle;
use Fixtures\Bundles\AnnotationsBundle\Entity\TestCustomIdGeneratorEntity;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;

class CustomIdGeneratorTest extends TestCase
{
public static function setUpBeforeClass(): void
{
if (interface_exists(EntityManagerInterface::class)) {
return;
}

self::markTestSkipped('This test requires ORM');
}

/**
* https://github.com/doctrine/orm/pull/7953 needed, otherwise ORM classes we define services for trigger deprecations
*
* @group legacy
*/
public function testRepositoryServiceWiring(): void
{
$container = new ContainerBuilder(new ParameterBag([
'kernel.debug' => false,
'kernel.bundles' => ['AnnotationsBundle' => AnnotationsBundle::class],
'kernel.cache_dir' => sys_get_temp_dir(),
'kernel.environment' => 'test',
'kernel.runtime_environment' => '%%env(default:kernel.environment:APP_RUNTIME_ENV)%%',
'kernel.build_dir' => __DIR__ . '/../../../../', // src dir
'kernel.root_dir' => __DIR__ . '/../../../../', // src dir
'kernel.project_dir' => __DIR__ . '/../../../../', // src dir
'kernel.bundles_metadata' => [],
'kernel.charset' => 'UTF-8',
'kernel.container_class' => ContainerBuilder::class,
'kernel.secret' => 'test',
'container.build_id' => uniqid(),
'env(base64:default::SYMFONY_DECRYPTION_SECRET)' => 'foo',
]));
$container->set('annotation_reader', new AnnotationReader());

$extension = new FrameworkExtension();
$container->registerExtension($extension);
$extension->load(['framework' => []], $container);

$extension = new DoctrineExtension();
$container->registerExtension($extension);
$extension->load([
[
'dbal' => [
'driver' => 'pdo_sqlite',
'charset' => 'UTF8',
],
'orm' => [
'mappings' => [
'AnnotationsBundle' => [
'type' => 'annotation',
'dir' => __DIR__ . '/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/Entity',
'prefix' => 'Fixtures\Bundles\AnnotationsBundle\Entity',
],
],
],
],
], $container);

$def = $container->register('my_id_generator', CustomIdGenerator::class)
->setPublic(false);

$def->setAutoconfigured(true);

$container->addCompilerPass(new IdGeneratorPass());
$container->compile();

$em = $container->get('doctrine.orm.default_entity_manager');
assert($em instanceof EntityManagerInterface);

$metadata = $em->getClassMetadata(TestCustomIdGeneratorEntity::class);
$this->assertInstanceOf(CustomIdGenerator::class, $metadata->idGenerator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Fixtures\Bundles\AnnotationsBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class TestCustomIdGeneratorEntity
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator("my_id_generator")
* @ORM\Column(type="integer")
*
* @var int
*/
public $id;
}
14 changes: 14 additions & 0 deletions Tests/DependencyInjection/Fixtures/CustomIdGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\Fixtures;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Id\AbstractIdGenerator;

class CustomIdGenerator extends AbstractIdGenerator
{
public function generate(EntityManager $em, $entity)
{
return 42;
}
}

0 comments on commit 8f92b94

Please sign in to comment.