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 Feb 12, 2021
1 parent 91f0139 commit e174049
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 10 deletions.
75 changes: 75 additions & 0 deletions DependencyInjection/Compiler/IdGeneratorPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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));

// 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 ($container->findTaggedServiceIds(self::CONFIGURATION_TAG) as $id => $tags) {
$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 ($metadataDriverImpl === null) {
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
28 changes: 28 additions & 0 deletions Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory as BaseClassMetadataFactory;

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);
}
}
11 changes: 11 additions & 0 deletions Resources/config/orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@
<service id="doctrine.orm.quote_strategy.default" class="%doctrine.orm.quote_strategy.default.class%" public="false" />
<service id="doctrine.orm.quote_strategy.ansi" class="%doctrine.orm.quote_strategy.ansi.class%" public="false" />

<!-- custom id generators -->
<service id="doctrine.ulid_generator" class="Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator">
<argument type="service" id="ulid.factory" on-invalid="ignore" />
<tag name="doctrine.id_generator" />
</service>

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

<!-- commands -->
<service id="doctrine.cache_clear_metadata_command" class="Doctrine\Bundle\DoctrineBundle\Command\Proxy\ClearMetadataCacheDoctrineCommand">
<tag name="console.command" command="doctrine:cache:clear-metadata" />
Expand Down
44 changes: 44 additions & 0 deletions Resources/doc/custom-id-generators.rts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Custom ID Generators
====================

Custom ID generators are classes that allow implementing custom logic to generate
identifiers for your entities. They extend ``Doctrine\ORM\Id\AbstractIdGenerator``
and implement the custom logic in the ``generate(EntityManager $em, $entity)``
method. Before Doctrine bundle 2.3, custom ID generators were always created
without any constructor arguments.

Starting with Doctrine bundle 2.3, the ``CustomIdGenerator`` annotation can be
used to reference any services tagged with the ``doctrine.id_generator`` tag.
If you enable autoconfiguration (which is the default most of the time), Symfony
will add this tag for you automatically if you implement your own id-generators.

When using Symfony's Doctrine bridge and Uid component 5.3 or higher, two services
are provided: ``doctrine.ulid_generator`` to generate ULIDs, and
``doctrine.uuid_generator`` to generate UUIDs.

.. code-block:: php

<?php
// User.php

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class User
{
/**
* @Id
* @Column(type="uuid")
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator('doctrine.uuid_generator')
*/
private $id;

// ....
}

See also
https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/annotations-reference.html#annref_customidgenerator
for more info about custom ID generators.
1 change: 1 addition & 0 deletions Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ configuration options, console commands and even a web debug toolbar collector.

installation
entity-listeners
custom-id-generators
configuration
5 changes: 0 additions & 5 deletions Tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public function testContainerWithDbalOnly(): void
}
}

/**
* https://github.com/doctrine/orm/pull/7953 needed, otherwise ORM classes we define services for trigger deprecations
*
* @group legacy
*/
public function testContainer(): void
{
if (! interface_exists(EntityManagerInterface::class)) {
Expand Down
Loading

0 comments on commit e174049

Please sign in to comment.