Skip to content

Commit a1268d4

Browse files
Enable @CustomIdGenerator() to reference services tagged as "doctrine.id_generator" (#1284)
1 parent cf98a24 commit a1268d4

File tree

13 files changed

+356
-10
lines changed

13 files changed

+356
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler;
4+
5+
use Doctrine\Bundle\DoctrineBundle\Mapping\ClassMetadataFactory;
6+
use Doctrine\Bundle\DoctrineBundle\Mapping\MappingDriver;
7+
use Doctrine\ORM\Mapping\ClassMetadataFactory as ORMClassMetadataFactory;
8+
use Symfony\Component\DependencyInjection\Alias;
9+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
10+
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\Reference;
13+
14+
final class IdGeneratorPass implements CompilerPassInterface
15+
{
16+
const ID_GENERATOR_TAG = 'doctrine.id_generator';
17+
const CONFIGURATION_TAG = 'doctrine.orm.configuration';
18+
19+
public function process(ContainerBuilder $container): void
20+
{
21+
$generatorIds = array_keys($container->findTaggedServiceIds(self::ID_GENERATOR_TAG));
22+
23+
// when ORM is not enabled
24+
if (! $container->hasDefinition('doctrine.orm.configuration') || ! $generatorIds) {
25+
return;
26+
}
27+
28+
$generatorRefs = array_map(static function ($id) {
29+
return new Reference($id);
30+
}, $generatorIds);
31+
32+
$ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs));
33+
$container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false));
34+
35+
foreach ($container->findTaggedServiceIds(self::CONFIGURATION_TAG) as $id => $tags) {
36+
$configurationDef = $container->getDefinition($id);
37+
$methodCalls = $configurationDef->getMethodCalls();
38+
$metadataDriverImpl = null;
39+
40+
foreach ($methodCalls as $i => [$method, $arguments]) {
41+
if ($method === 'setMetadataDriverImpl') {
42+
$metadataDriverImpl = (string) $arguments[0];
43+
}
44+
45+
if ($method !== 'setClassMetadataFactoryName') {
46+
continue;
47+
}
48+
49+
if ($arguments[0] !== ORMClassMetadataFactory::class && $arguments[0] !== ClassMetadataFactory::class) {
50+
$class = $container->getReflectionClass($arguments[0]);
51+
52+
if ($class && $class->isSubclassOf(ClassMetadataFactory::class)) {
53+
break;
54+
}
55+
56+
continue 2;
57+
}
58+
59+
$methodCalls[$i] = ['setClassMetadataFactoryName', [ClassMetadataFactory::class]];
60+
}
61+
62+
if ($metadataDriverImpl === null) {
63+
continue;
64+
}
65+
66+
$configurationDef->setMethodCalls($methodCalls);
67+
$container->register('.' . $metadataDriverImpl, MappingDriver::class)
68+
->setDecoratedService($metadataDriverImpl)
69+
->setArguments([
70+
new Reference(sprintf('.%s.inner', $metadataDriverImpl)),
71+
new Reference('doctrine.id_generator_locator'),
72+
]);
73+
}
74+
}
75+
}

DependencyInjection/DoctrineExtension.php

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\ImportDoctrineCommand;
66
use Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider;
77
use Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter;
8+
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
89
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
910
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
1011
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
@@ -15,10 +16,13 @@
1516
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
1617
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
1718
use Doctrine\ORM\EntityManagerInterface;
19+
use Doctrine\ORM\Id\AbstractIdGenerator;
1820
use Doctrine\ORM\Proxy\Autoloader;
1921
use Doctrine\ORM\UnitOfWork;
2022
use LogicException;
2123
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
24+
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
25+
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
2226
use Symfony\Bridge\Doctrine\Messenger\DoctrineClearEntityManagerWorkerSubscriber;
2327
use Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware;
2428
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
@@ -405,6 +409,14 @@ protected function ormLoad(array $config, ContainerBuilder $container)
405409
$container->removeDefinition('doctrine.orm.listeners.pdo_cache_adapter_doctrine_schema_subscriber');
406410
}
407411

412+
if (! class_exists(UlidGenerator::class)) {
413+
$container->removeDefinition('doctrine.ulid_generator');
414+
}
415+
416+
if (! class_exists(UuidGenerator::class)) {
417+
$container->removeDefinition('doctrine.uuid_generator');
418+
}
419+
408420
$entityManagers = [];
409421
foreach (array_keys($config['entity_managers']) as $name) {
410422
$entityManagers[$name] = sprintf('doctrine.orm.%s_entity_manager', $name);
@@ -459,6 +471,9 @@ protected function ormLoad(array $config, ContainerBuilder $container)
459471
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
460472
->addTag('doctrine.event_subscriber');
461473

474+
$container->registerForAutoconfiguration(AbstractIdGenerator::class)
475+
->addTag(IdGeneratorPass::ID_GENERATOR_TAG);
476+
462477
/**
463478
* @see DoctrineBundle::boot()
464479
*/
@@ -477,6 +492,7 @@ protected function ormLoad(array $config, ContainerBuilder $container)
477492
protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $container)
478493
{
479494
$ormConfigDef = $container->setDefinition(sprintf('doctrine.orm.%s_configuration', $entityManager['name']), new ChildDefinition('doctrine.orm.configuration'));
495+
$ormConfigDef->addTag(IdGeneratorPass::CONFIGURATION_TAG);
480496

481497
$this->loadOrmEntityManagerMappingInformation($entityManager, $ormConfigDef, $container);
482498
$this->loadOrmCacheDrivers($entityManager, $container);

DoctrineBundle.php

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\CacheSchemaSubscriberPass;
66
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass;
77
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass;
8+
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\IdGeneratorPass;
89
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
910
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass;
1011
use Doctrine\Common\Util\ClassUtils;
@@ -42,6 +43,7 @@ public function build(ContainerBuilder $container)
4243
$container->addCompilerPass(new DoctrineValidationPass('orm'));
4344
$container->addCompilerPass(new EntityListenerPass());
4445
$container->addCompilerPass(new ServiceRepositoryCompilerPass());
46+
$container->addCompilerPass(new IdGeneratorPass());
4547
$container->addCompilerPass(new WellKnownSchemaFilterPass());
4648
$container->addCompilerPass(new DbalSchemaFilterPass());
4749
$container->addCompilerPass(new CacheSchemaSubscriberPass(), PassConfig::TYPE_BEFORE_REMOVING, -10);

Mapping/ClassMetadataFactory.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
4+
5+
use Doctrine\ORM\Mapping\ClassMetadata;
6+
use Doctrine\ORM\Mapping\ClassMetadataFactory as BaseClassMetadataFactory;
7+
8+
class ClassMetadataFactory extends BaseClassMetadataFactory
9+
{
10+
/**
11+
* {@inheritDoc}
12+
*/
13+
protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents): void
14+
{
15+
parent::doLoadMetadata($class, $parent, $rootEntityFound, $nonSuperclassParents);
16+
17+
$customGeneratorDefinition = $class->customGeneratorDefinition;
18+
19+
if (! isset($customGeneratorDefinition['instance'])) {
20+
return;
21+
}
22+
23+
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM);
24+
$class->setIdGenerator($customGeneratorDefinition['instance']);
25+
unset($customGeneratorDefinition['instance']);
26+
$class->setCustomGeneratorDefinition($customGeneratorDefinition);
27+
}
28+
}

Mapping/MappingDriver.php

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
4+
5+
use Doctrine\ORM\Mapping\ClassMetadataInfo;
6+
use Doctrine\Persistence\Mapping\ClassMetadata;
7+
use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface;
8+
use Psr\Container\ContainerInterface;
9+
10+
class MappingDriver implements MappingDriverInterface
11+
{
12+
/** @var MappingDriverInterface */
13+
private $driver;
14+
15+
/** @var ContainerInterface */
16+
private $idGeneratorLocator;
17+
18+
public function __construct(MappingDriverInterface $driver, ContainerInterface $idGeneratorLocator)
19+
{
20+
$this->driver = $driver;
21+
$this->idGeneratorLocator = $idGeneratorLocator;
22+
}
23+
24+
/**
25+
* {@inheritDoc}
26+
*/
27+
public function getAllClassNames()
28+
{
29+
return $this->driver->getAllClassNames();
30+
}
31+
32+
/**
33+
* {@inheritDoc}
34+
*/
35+
public function isTransient($className): bool
36+
{
37+
return $this->driver->isTransient($className);
38+
}
39+
40+
/**
41+
* {@inheritDoc}
42+
*/
43+
public function loadMetadataForClass($className, ClassMetadata $metadata): void
44+
{
45+
$this->driver->loadMetadataForClass($className, $metadata);
46+
47+
if (
48+
$metadata->generatorType !== ClassMetadataInfo::GENERATOR_TYPE_CUSTOM
49+
|| ! isset($metadata->customGeneratorDefinition['class'])
50+
|| ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class'])
51+
) {
52+
return;
53+
}
54+
55+
$idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']);
56+
$metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition);
57+
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
58+
}
59+
}

Resources/config/orm.xml

+11
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@
150150
<service id="doctrine.orm.quote_strategy.default" class="%doctrine.orm.quote_strategy.default.class%" public="false" />
151151
<service id="doctrine.orm.quote_strategy.ansi" class="%doctrine.orm.quote_strategy.ansi.class%" public="false" />
152152

153+
<!-- custom id generators -->
154+
<service id="doctrine.ulid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator">
155+
<argument type="service" id="ulid.factory" on-invalid="ignore" />
156+
<tag name="doctrine.id_generator" />
157+
</service>
158+
159+
<service id="doctrine.uuid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator">
160+
<argument type="service" id="uuid.factory" on-invalid="ignore" />
161+
<tag name="doctrine.id_generator" />
162+
</service>
163+
153164
<!-- commands -->
154165
<service id="doctrine.cache_clear_metadata_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearMetadataCacheDoctrineCommand">
155166
<tag name="console.command" command="doctrine:cache:clear-metadata" />
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Custom ID Generators
2+
====================
3+
4+
Custom ID generators are classes that allow implementing custom logic to generate
5+
identifiers for your entities. They extend ``Doctrine\ORM\Id\AbstractIdGenerator``
6+
and implement the custom logic in the ``generate(EntityManager $em, $entity)``
7+
method. Before Doctrine bundle 2.3, custom ID generators were always created
8+
without any constructor arguments.
9+
10+
Starting with Doctrine bundle 2.3, the ``CustomIdGenerator`` annotation can be
11+
used to reference any services tagged with the ``doctrine.id_generator`` tag.
12+
If you enable autoconfiguration (which is the default most of the time), Symfony
13+
will add this tag for you automatically if you implement your own id-generators.
14+
15+
When using Symfony's Doctrine bridge and Uid component 5.3 or higher, two services
16+
are provided: ``doctrine.ulid_generator`` to generate ULIDs, and
17+
``doctrine.uuid_generator`` to generate UUIDs.
18+
19+
.. code-block:: php
20+
21+
<?php
22+
// User.php
23+
24+
use Doctrine\ORM\Mapping as ORM;
25+
26+
/**
27+
* @ORM\Entity
28+
*/
29+
class User
30+
{
31+
/**
32+
* @Id
33+
* @Column(type="uuid")
34+
* @ORM\GeneratedValue(strategy="CUSTOM")
35+
* @ORM\CustomIdGenerator('doctrine.uuid_generator')
36+
*/
37+
private $id;
38+
39+
// ....
40+
}
41+
42+
See also
43+
https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/annotations-reference.html#annref_customidgenerator
44+
for more info about custom ID generators.

Resources/doc/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ configuration options, console commands and even a web debug toolbar collector.
88

99
installation
1010
entity-listeners
11+
custom-id-generators
1112
configuration

Tests/ContainerTest.php

-5
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ public function testContainerWithDbalOnly(): void
4242
}
4343
}
4444

45-
/**
46-
* https://github.com/doctrine/orm/pull/7953 needed, otherwise ORM classes we define services for trigger deprecations
47-
*
48-
* @group legacy
49-
*/
5045
public function testContainer(): void
5146
{
5247
if (! interface_exists(EntityManagerInterface::class)) {

0 commit comments

Comments
 (0)