Skip to content

Commit

Permalink
Compatibility with ORM 3
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Nov 11, 2023
1 parent e168b42 commit 08aee56
Show file tree
Hide file tree
Showing 23 changed files with 383 additions and 226 deletions.
4 changes: 2 additions & 2 deletions DataCollector/DoctrineDataCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
Expand Down Expand Up @@ -105,7 +105,7 @@ public function collect(Request $request, Response $response, ?Throwable $except
assert($factory instanceof AbstractClassMetadataFactory);

foreach ($factory->getLoadedMetadata() as $class) {
assert($class instanceof ClassMetadataInfo);
assert($class instanceof ClassMetadata);
if (isset($entities[$name][$class->getName()])) {
continue;
}
Expand Down
4 changes: 1 addition & 3 deletions DependencyInjection/Compiler/IdGeneratorPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ public function process(ContainerBuilder $container): void
return;
}

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

$ref = ServiceLocatorTagPass::register($container, array_combine($generatorIds, $generatorRefs));
$container->setAlias('doctrine.id_generator_locator', new Alias((string) $ref, false));
Expand Down
9 changes: 5 additions & 4 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;

use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Proxy\ProxyFactory;
use InvalidArgumentException;
use ReflectionClass;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
Expand All @@ -32,6 +32,7 @@
use function is_int;
use function is_string;
use function key;
use function method_exists;
use function reset;
use function sprintf;
use function strlen;
Expand Down Expand Up @@ -498,11 +499,11 @@ private function addOrmSection(ArrayNodeDefinition $node): void
->validate()
->ifString()
->then(static function ($v) {
return constant('Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_' . strtoupper($v));
return constant('Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_' . strtoupper($v));
})
->end()
->end()
->booleanNode('enable_lazy_ghost_objects')->defaultFalse()
->booleanNode('enable_lazy_ghost_objects')->defaultValue(! method_exists(ProxyFactory::class, 'resetUninitializedProxy'))
->end()
->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end()
->scalarNode('proxy_namespace')->defaultValue('Proxies')->end()
Expand Down Expand Up @@ -832,7 +833,7 @@ private function getAutoGenerateModes(): array
{
$constPrefix = 'AUTOGENERATE_';
$prefixLen = strlen($constPrefix);
$refClass = new ReflectionClass(AbstractProxyFactory::class);
$refClass = new ReflectionClass(ProxyFactory::class);
$constsArray = $refClass->getConstants();
$namesArray = [];
$valuesArray = [];
Expand Down
24 changes: 10 additions & 14 deletions DependencyInjection/DoctrineExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,19 @@
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
use Doctrine\ORM\Configuration as OrmConfiguration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
use Doctrine\ORM\Proxy\Autoloader;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
use Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
use InvalidArgumentException;
use LogicException;
use ReflectionMethod;
use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
Expand Down Expand Up @@ -75,6 +74,9 @@
use function sprintf;
use function str_replace;
use function trait_exists;
use function trigger_deprecation;

use const PHP_VERSION_ID;

/**
* DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
Expand Down Expand Up @@ -438,11 +440,6 @@ protected function ormLoad(array $config, ContainerBuilder $container)
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('orm.xml');

if (! (new ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) {
$container->getDefinition('doctrine.orm.entity_manager.abstract')
->setFactory(['%doctrine.orm.entity_manager.class%', 'create']);
}

if (class_exists(AbstractType::class)) {
$container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
}
Expand Down Expand Up @@ -545,13 +542,6 @@ protected function ormLoad(array $config, ContainerBuilder $container)
$container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);

if ($config['enable_lazy_ghost_objects'] ?? false) {
if (! method_exists(OrmConfiguration::class, 'setLazyGhostObjectEnabled')) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "doctrine/orm" library'
. ' version 2.14 or higher is not installed. Please run "composer update doctrine/orm".',
);
}

// available in Symfony 6.2 and higher
/** @psalm-suppress UndefinedClass */
if (! trait_exists(LazyGhostTrait::class)) {
Expand All @@ -567,6 +557,12 @@ protected function ormLoad(array $config, ContainerBuilder $container)
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".',
);
}
} elseif (! method_exists(ProxyFactory::class, 'resetUninitializedProxy')) {
throw new LogicException(
'Lazy ghost objects cannot be disabled for ORM 3.',
);

Check warning on line 563 in DependencyInjection/DoctrineExtension.php

View check run for this annotation

Codecov / codecov/patch

DependencyInjection/DoctrineExtension.php#L561-L563

Added lines #L561 - L563 were not covered by tests
} elseif (PHP_VERSION_ID >= 80100) {
trigger_deprecation('doctrine/doctrine-bundle', '2.11', 'Not setting "enable_lazy_ghost_objects" to true is deprecated.');
}

$options = ['auto_generate_proxy_classes', 'enable_lazy_ghost_objects', 'proxy_dir', 'proxy_namespace'];
Expand Down
9 changes: 4 additions & 5 deletions Mapping/ContainerEntityListenerResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(ContainerInterface $container)
/**
* {@inheritDoc}
*/
public function clear($className = null)
public function clear($className = null): void
{
if ($className === null) {
$this->instances = [];
Expand All @@ -48,7 +48,7 @@ public function clear($className = null)
/**
* {@inheritDoc}
*/
public function register($object)
public function register($object): void
{
if (! is_object($object)) {
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
Expand All @@ -70,7 +70,7 @@ public function registerService($className, $serviceId)
/**
* {@inheritDoc}
*/
public function resolve($className)
public function resolve($className): object
{
$className = $this->normalizeClassName($className);

Expand All @@ -85,8 +85,7 @@ public function resolve($className)
return $this->instances[$className];
}

/** @return object */
private function resolveService(string $serviceId)
private function resolveService(string $serviceId): object
{
if (! $this->container->has($serviceId)) {
throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));
Expand Down
8 changes: 4 additions & 4 deletions Mapping/MappingDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Doctrine\Bundle\DoctrineBundle\Mapping;

use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver as MappingDriverInterface;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -42,8 +42,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata): void
$this->driver->loadMetadataForClass($className, $metadata);

if (
! $metadata instanceof ClassMetadataInfo
|| $metadata->generatorType !== ClassMetadataInfo::GENERATOR_TYPE_CUSTOM
! $metadata instanceof OrmClassMetadata
|| $metadata->generatorType !== OrmClassMetadata::GENERATOR_TYPE_CUSTOM
|| ! isset($metadata->customGeneratorDefinition['class'])
|| ! $this->idGeneratorLocator->has($metadata->customGeneratorDefinition['class'])
) {
Expand All @@ -52,7 +52,7 @@ public function loadMetadataForClass($className, ClassMetadata $metadata): void

$idGenerator = $this->idGeneratorLocator->get($metadata->customGeneratorDefinition['class']);
$metadata->setCustomGeneratorDefinition(['instance' => $idGenerator] + $metadata->customGeneratorDefinition);
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE);
$metadata->setIdGeneratorType(OrmClassMetadata::GENERATOR_TYPE_NONE);
}

/**
Expand Down
22 changes: 19 additions & 3 deletions Repository/ContainerRepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@

use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\ObjectRepository;
use Psr\Container\ContainerInterface;
use RuntimeException;

use function class_exists;
use function get_debug_type;
use function is_a;
use function spl_object_hash;
use function sprintf;
use function trigger_deprecation;

/**
* Fetches repositories from the container or falls back to normal creation.
*/
final class ContainerRepositoryFactory implements RepositoryFactory
{
use RepositoryFactoryCompatibility;

/** @var array<string, ObjectRepository> */
private array $managedRepositories = [];

Expand All @@ -32,11 +37,14 @@ public function __construct(ContainerInterface $container)
}

/**
* {@inheritDoc}
* @param class-string<T> $entityName
*
* @return ObjectRepository<T>
* @psalm-return ($strictTypeCheck is true ? EntityRepository<T> : ObjectRepository<T>)
*
* @template T of object
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
private function doGetRepository(EntityManagerInterface $entityManager, string $entityName, bool $strictTypeCheck): ObjectRepository
{
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryServiceId = $metadata->customRepositoryClassName;
Expand All @@ -47,8 +55,16 @@ public function getRepository(EntityManagerInterface $entityManager, $entityName
if ($this->container->has($customRepositoryName)) {
$repository = $this->container->get($customRepositoryName);

if (! $repository instanceof EntityRepository && $strictTypeCheck) {
throw new RuntimeException(sprintf('The service "%s" must extends EntityRepository (e.g. by extending ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository)));
}

if (! $repository instanceof ObjectRepository) {
throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository).', $repositoryServiceId));
throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository)));
}

if (! $repository instanceof EntityRepository) {
trigger_deprecation('doctrine/doctrine-bundle', '2.11', 'The service "%s" of type "%s" should extend "%s", not doing so is deprecated.', $repositoryServiceId, get_debug_type($repository), EntityRepository::class);

Check warning on line 67 in Repository/ContainerRepositoryFactory.php

View check run for this annotation

Codecov / codecov/patch

Repository/ContainerRepositoryFactory.php#L67

Added line #L67 was not covered by tests
}

/** @psalm-var ObjectRepository<T> */
Expand Down
51 changes: 51 additions & 0 deletions Repository/LegacyServiceEntityRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Doctrine\Bundle\DoctrineBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use LogicException;

use function sprintf;

/**
* Optional EntityRepository base class with a simplified constructor (for autowiring).
*
* To use in your class, inject the "registry" service and call
* the parent constructor. For example:
*
* class YourEntityRepository extends ServiceEntityRepository
* {
* public function __construct(ManagerRegistry $registry)
* {
* parent::__construct($registry, YourEntity::class);
* }
* }
*
* @internal Extend {@see ServiceEntityRepository} instead.
*
* @template T of object
* @template-extends EntityRepository<T>
*/
class LegacyServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
{
/**
* @param string $entityClass The class name of the entity this repository manages
* @psalm-param class-string<T> $entityClass
*/
public function __construct(ManagerRegistry $registry, string $entityClass)

Check warning on line 38 in Repository/LegacyServiceEntityRepository.php

View check run for this annotation

Codecov / codecov/patch

Repository/LegacyServiceEntityRepository.php#L38

Added line #L38 was not covered by tests
{
$manager = $registry->getManagerForClass($entityClass);

Check warning on line 40 in Repository/LegacyServiceEntityRepository.php

View check run for this annotation

Codecov / codecov/patch

Repository/LegacyServiceEntityRepository.php#L40

Added line #L40 was not covered by tests

if ($manager === null) {
throw new LogicException(sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.',
$entityClass,
));

Check warning on line 46 in Repository/LegacyServiceEntityRepository.php

View check run for this annotation

Codecov / codecov/patch

Repository/LegacyServiceEntityRepository.php#L42-L46

Added lines #L42 - L46 were not covered by tests
}

parent::__construct($manager, $manager->getClassMetadata($entityClass));

Check warning on line 49 in Repository/LegacyServiceEntityRepository.php

View check run for this annotation

Codecov / codecov/patch

Repository/LegacyServiceEntityRepository.php#L49

Added line #L49 was not covered by tests
}
}
39 changes: 39 additions & 0 deletions Repository/RepositoryFactoryCompatibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Doctrine\Bundle\DoctrineBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\ObjectRepository;
use ReflectionMethod;

if ((new ReflectionMethod(RepositoryFactory::class, 'getRepository'))->hasReturnType()) {
/** @internal */
trait RepositoryFactoryCompatibility
{
/**
* Gets the repository for an entity class.
*
* @param class-string<T> $entityName
*
* @return EntityRepository<T>
*
* @template T of object
*/
public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository
{
return $this->doGetRepository($entityManager, $entityName, true);
}
}
} else {
/** @internal */
trait RepositoryFactoryCompatibility
{
/** {@inheritDoc} */
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
return $this->doGetRepository($entityManager, $entityName, false);
}
}
}
Loading

0 comments on commit 08aee56

Please sign in to comment.