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

Enable @CustomIdGenerator() to reference services tagged as "doctrine.id_generator" #1284

Merged
merged 1 commit into from
Feb 15, 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
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>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added back now that symfony/symfony#39507 is merged


<!-- 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.rst
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