Skip to content

Commit 7fd7671

Browse files
authored
Compatibility with ORM 3 (#1722)
1 parent d7f67f5 commit 7fd7671

26 files changed

+444
-231
lines changed

DataCollector/DoctrineDataCollector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
99
use Doctrine\ORM\Configuration;
1010
use Doctrine\ORM\EntityManagerInterface;
11-
use Doctrine\ORM\Mapping\ClassMetadataInfo;
11+
use Doctrine\ORM\Mapping\ClassMetadata;
1212
use Doctrine\ORM\Tools\SchemaValidator;
1313
use Doctrine\Persistence\ManagerRegistry;
1414
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
@@ -105,7 +105,7 @@ public function collect(Request $request, Response $response, ?Throwable $except
105105
assert($factory instanceof AbstractClassMetadataFactory);
106106

107107
foreach ($factory->getLoadedMetadata() as $class) {
108-
assert($class instanceof ClassMetadataInfo);
108+
assert($class instanceof ClassMetadata);
109109
if (isset($entities[$name][$class->getName()])) {
110110
continue;
111111
}

DependencyInjection/Configuration.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
namespace Doctrine\Bundle\DoctrineBundle\DependencyInjection;
44

5-
use Doctrine\Common\Proxy\AbstractProxyFactory;
65
use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
76
use Doctrine\ORM\EntityManager;
87
use Doctrine\ORM\EntityRepository;
98
use Doctrine\ORM\Mapping\ClassMetadataFactory;
9+
use Doctrine\ORM\Proxy\ProxyFactory;
1010
use InvalidArgumentException;
1111
use ReflectionClass;
1212
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
@@ -32,6 +32,7 @@
3232
use function is_int;
3333
use function is_string;
3434
use function key;
35+
use function method_exists;
3536
use function reset;
3637
use function sprintf;
3738
use function strlen;
@@ -499,11 +500,11 @@ private function addOrmSection(ArrayNodeDefinition $node): void
499500
->validate()
500501
->ifString()
501502
->then(static function ($v) {
502-
return constant('Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_' . strtoupper($v));
503+
return constant('Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_' . strtoupper($v));
503504
})
504505
->end()
505506
->end()
506-
->booleanNode('enable_lazy_ghost_objects')->defaultFalse()
507+
->booleanNode('enable_lazy_ghost_objects')->defaultValue(! method_exists(ProxyFactory::class, 'resetUninitializedProxy'))
507508
->end()
508509
->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/orm/Proxies')->end()
509510
->scalarNode('proxy_namespace')->defaultValue('Proxies')->end()
@@ -833,7 +834,7 @@ private function getAutoGenerateModes(): array
833834
{
834835
$constPrefix = 'AUTOGENERATE_';
835836
$prefixLen = strlen($constPrefix);
836-
$refClass = new ReflectionClass(AbstractProxyFactory::class);
837+
$refClass = new ReflectionClass(ProxyFactory::class);
837838
$constsArray = $refClass->getConstants();
838839
$namesArray = [];
839840
$valuesArray = [];

DependencyInjection/DoctrineExtension.php

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,19 @@
1919
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
2020
use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
2121
use Doctrine\ORM\Configuration as OrmConfiguration;
22-
use Doctrine\ORM\EntityManager;
2322
use Doctrine\ORM\EntityManagerInterface;
2423
use Doctrine\ORM\Events;
2524
use Doctrine\ORM\Id\AbstractIdGenerator;
2625
use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
2726
use Doctrine\ORM\Proxy\Autoloader;
27+
use Doctrine\ORM\Proxy\ProxyFactory;
2828
use Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand;
2929
use Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand;
3030
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
3131
use Doctrine\ORM\UnitOfWork;
3232
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
3333
use InvalidArgumentException;
3434
use LogicException;
35-
use ReflectionMethod;
3635
use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver;
3736
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
3837
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
@@ -75,6 +74,9 @@
7574
use function sprintf;
7675
use function str_replace;
7776
use function trait_exists;
77+
use function trigger_deprecation;
78+
79+
use const PHP_VERSION_ID;
7880

7981
/**
8082
* DoctrineExtension is an extension for the Doctrine DBAL and ORM library.
@@ -442,11 +444,6 @@ protected function ormLoad(array $config, ContainerBuilder $container)
442444
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
443445
$loader->load('orm.xml');
444446

445-
if (! (new ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) {
446-
$container->getDefinition('doctrine.orm.entity_manager.abstract')
447-
->setFactory(['%doctrine.orm.entity_manager.class%', 'create']);
448-
}
449-
450447
if (class_exists(AbstractType::class)) {
451448
$container->getDefinition('form.type.entity')->addTag('kernel.reset', ['method' => 'reset']);
452449
}
@@ -549,13 +546,6 @@ protected function ormLoad(array $config, ContainerBuilder $container)
549546
$container->setParameter('doctrine.default_entity_manager', $config['default_entity_manager']);
550547

551548
if ($config['enable_lazy_ghost_objects'] ?? false) {
552-
if (! method_exists(OrmConfiguration::class, 'setLazyGhostObjectEnabled')) {
553-
throw new LogicException(
554-
'Lazy ghost objects cannot be enabled because the "doctrine/orm" library'
555-
. ' version 2.14 or higher is not installed. Please run "composer update doctrine/orm".',
556-
);
557-
}
558-
559549
// available in Symfony 6.2 and higher
560550
/** @psalm-suppress UndefinedClass */
561551
if (! trait_exists(LazyGhostTrait::class)) {
@@ -571,6 +561,12 @@ protected function ormLoad(array $config, ContainerBuilder $container)
571561
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".',
572562
);
573563
}
564+
} elseif (! method_exists(ProxyFactory::class, 'resetUninitializedProxy')) {
565+
throw new LogicException(
566+
'Lazy ghost objects cannot be disabled for ORM 3.',
567+
);
568+
} elseif (PHP_VERSION_ID >= 80100) {
569+
trigger_deprecation('doctrine/doctrine-bundle', '2.11', 'Not setting "enable_lazy_ghost_objects" to true is deprecated.');
574570
}
575571

576572
$options = ['auto_generate_proxy_classes', 'enable_lazy_ghost_objects', 'proxy_dir', 'proxy_namespace'];

Mapping/ContainerEntityListenerResolver.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function __construct(ContainerInterface $container)
3232
/**
3333
* {@inheritDoc}
3434
*/
35-
public function clear($className = null)
35+
public function clear($className = null): void
3636
{
3737
if ($className === null) {
3838
$this->instances = [];
@@ -48,7 +48,7 @@ public function clear($className = null)
4848
/**
4949
* {@inheritDoc}
5050
*/
51-
public function register($object)
51+
public function register($object): void
5252
{
5353
if (! is_object($object)) {
5454
throw new InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
@@ -70,7 +70,7 @@ public function registerService($className, $serviceId)
7070
/**
7171
* {@inheritDoc}
7272
*/
73-
public function resolve($className)
73+
public function resolve($className): object
7474
{
7575
$className = $this->normalizeClassName($className);
7676

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

88-
/** @return object */
89-
private function resolveService(string $serviceId)
88+
private function resolveService(string $serviceId): object
9089
{
9190
if (! $this->container->has($serviceId)) {
9291
throw new RuntimeException(sprintf('There is no service named "%s"', $serviceId));

Mapping/MappingDriver.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Doctrine\Bundle\DoctrineBundle\Mapping;
44

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

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

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

5858
/**

Repository/ContainerRepositoryFactory.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,27 @@
44

55
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass;
66
use Doctrine\ORM\EntityManagerInterface;
7+
use Doctrine\ORM\EntityRepository;
78
use Doctrine\ORM\Mapping\ClassMetadata;
89
use Doctrine\ORM\Repository\RepositoryFactory;
910
use Doctrine\Persistence\ObjectRepository;
1011
use Psr\Container\ContainerInterface;
1112
use RuntimeException;
1213

1314
use function class_exists;
15+
use function get_debug_type;
1416
use function is_a;
1517
use function spl_object_hash;
1618
use function sprintf;
19+
use function trigger_deprecation;
1720

1821
/**
1922
* Fetches repositories from the container or falls back to normal creation.
2023
*/
2124
final class ContainerRepositoryFactory implements RepositoryFactory
2225
{
26+
use RepositoryFactoryCompatibility;
27+
2328
/** @var array<string, ObjectRepository> */
2429
private array $managedRepositories = [];
2530

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

3439
/**
35-
* {@inheritDoc}
40+
* @param class-string<T> $entityName
41+
*
42+
* @return ObjectRepository<T>
43+
* @psalm-return ($strictTypeCheck is true ? EntityRepository<T> : ObjectRepository<T>)
3644
*
3745
* @template T of object
3846
*/
39-
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
47+
private function doGetRepository(EntityManagerInterface $entityManager, string $entityName, bool $strictTypeCheck): ObjectRepository
4048
{
4149
$metadata = $entityManager->getClassMetadata($entityName);
4250
$repositoryServiceId = $metadata->customRepositoryClassName;
@@ -47,8 +55,16 @@ public function getRepository(EntityManagerInterface $entityManager, $entityName
4755
if ($this->container->has($customRepositoryName)) {
4856
$repository = $this->container->get($customRepositoryName);
4957

58+
if (! $repository instanceof EntityRepository && $strictTypeCheck) {
59+
throw new RuntimeException(sprintf('The service "%s" must extend EntityRepository (e.g. by extending ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository)));
60+
}
61+
5062
if (! $repository instanceof ObjectRepository) {
51-
throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository).', $repositoryServiceId));
63+
throw new RuntimeException(sprintf('The service "%s" must implement ObjectRepository (or extend a base class, like ServiceEntityRepository), "%s" given.', $repositoryServiceId, get_debug_type($repository)));
64+
}
65+
66+
if (! $repository instanceof EntityRepository) {
67+
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);
5268
}
5369

5470
/** @psalm-var ObjectRepository<T> */

Repository/LazyServiceEntityRepository.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,7 @@
1111
use function sprintf;
1212

1313
/**
14-
* Optional EntityRepository base class with a simplified constructor (for autowiring).
15-
*
16-
* To use in your class, inject the "registry" service and call
17-
* the parent constructor. For example:
18-
*
19-
* class YourEntityRepository extends ServiceEntityRepository
20-
* {
21-
* public function __construct(ManagerRegistry $registry)
22-
* {
23-
* parent::__construct($registry, YourEntity::class);
24-
* }
25-
* }
26-
*
27-
* @internal to be renamed ServiceEntityRepository when PHP 8.1 / Symfony 6.2 becomes required
14+
* @internal Extend {@see ServiceEntityRepository} instead.
2815
*
2916
* @template T of object
3017
* @template-extends EntityRepository<T>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Bundle\DoctrineBundle\Repository;
6+
7+
use Doctrine\ORM\EntityRepository;
8+
use Doctrine\Persistence\ManagerRegistry;
9+
use LogicException;
10+
11+
use function sprintf;
12+
13+
/**
14+
* @internal Extend {@see ServiceEntityRepository} instead.
15+
*
16+
* @template T of object
17+
* @template-extends EntityRepository<T>
18+
*/
19+
class LegacyServiceEntityRepository extends EntityRepository implements ServiceEntityRepositoryInterface
20+
{
21+
/**
22+
* @param string $entityClass The class name of the entity this repository manages
23+
* @psalm-param class-string<T> $entityClass
24+
*/
25+
public function __construct(ManagerRegistry $registry, string $entityClass)
26+
{
27+
$manager = $registry->getManagerForClass($entityClass);
28+
29+
if ($manager === null) {
30+
throw new LogicException(sprintf(
31+
'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.',
32+
$entityClass,
33+
));
34+
}
35+
36+
parent::__construct($manager, $manager->getClassMetadata($entityClass));
37+
}
38+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\DoctrineBundle\Repository;
4+
5+
use Doctrine\ORM\EntityManagerInterface;
6+
use Doctrine\ORM\EntityRepository;
7+
use Doctrine\ORM\Repository\RepositoryFactory;
8+
use Doctrine\Persistence\ObjectRepository;
9+
use ReflectionMethod;
10+
11+
if ((new ReflectionMethod(RepositoryFactory::class, 'getRepository'))->hasReturnType()) {
12+
// ORM >= 3
13+
/** @internal */
14+
trait RepositoryFactoryCompatibility
15+
{
16+
/**
17+
* Gets the repository for an entity class.
18+
*
19+
* @param class-string<T> $entityName
20+
*
21+
* @return EntityRepository<T>
22+
*
23+
* @template T of object
24+
*
25+
* @psalm-suppress MethodSignatureMismatch
26+
*/
27+
public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository
28+
{
29+
return $this->doGetRepository($entityManager, $entityName, true);
30+
}
31+
}
32+
} else {
33+
// ORM 2
34+
/** @internal */
35+
trait RepositoryFactoryCompatibility
36+
{
37+
/** {@inheritDoc} */
38+
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
39+
{
40+
return $this->doGetRepository($entityManager, $entityName, false);
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)