Skip to content

Commit

Permalink
Compatibility with ORM 3 (#1722)
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus authored Nov 12, 2023
1 parent d7f67f5 commit 7fd7671
Show file tree
Hide file tree
Showing 26 changed files with 444 additions and 231 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
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 @@ -499,11 +500,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 @@ -833,7 +834,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 @@ -442,11 +444,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 @@ -549,13 +546,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 @@ -571,6 +561,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.',
);
} 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 extend 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);
}

/** @psalm-var ObjectRepository<T> */
Expand Down
15 changes: 1 addition & 14 deletions Repository/LazyServiceEntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,7 @@
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 to be renamed ServiceEntityRepository when PHP 8.1 / Symfony 6.2 becomes required
* @internal Extend {@see ServiceEntityRepository} instead.
*
* @template T of object
* @template-extends EntityRepository<T>
Expand Down
38 changes: 38 additions & 0 deletions Repository/LegacyServiceEntityRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Doctrine\Bundle\DoctrineBundle\Repository;

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

use function sprintf;

/**
* @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)
{
$manager = $registry->getManagerForClass($entityClass);

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,
));
}

parent::__construct($manager, $manager->getClassMetadata($entityClass));
}
}
43 changes: 43 additions & 0 deletions Repository/RepositoryFactoryCompatibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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()) {
// ORM >= 3
/** @internal */
trait RepositoryFactoryCompatibility
{
/**
* Gets the repository for an entity class.
*
* @param class-string<T> $entityName
*
* @return EntityRepository<T>
*
* @template T of object
*
* @psalm-suppress MethodSignatureMismatch
*/
public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository
{
return $this->doGetRepository($entityManager, $entityName, true);
}
}
} else {
// ORM 2
/** @internal */
trait RepositoryFactoryCompatibility
{
/** {@inheritDoc} */
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
return $this->doGetRepository($entityManager, $entityName, false);
}
}
}
Loading

0 comments on commit 7fd7671

Please sign in to comment.