diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dfba1d56b5f..21d212a509e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -39,8 +39,6 @@ jobs: - "default" extension: - "pdo_sqlite" - proxy: - - "common" deps: - "highest" include: @@ -53,10 +51,6 @@ jobs: - php-version: "8.2" dbal-version: "default" extension: "sqlite3" - - php-version: "8.1" - dbal-version: "default" - proxy: "lazy-ghost" - extension: "pdo_sqlite" - php-version: "8.1" dbal-version: "default" deps: "lowest" @@ -90,13 +84,11 @@ jobs: run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml" env: ENABLE_SECOND_LEVEL_CACHE: 0 - ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}" - name: "Run PHPUnit with Second Level Cache" run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml" env: ENABLE_SECOND_LEVEL_CACHE: 1 - ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}" - name: "Upload coverage file" uses: "actions/upload-artifact@v3" diff --git a/UPGRADE.md b/UPGRADE.md index e6b53eb228e..8e57362503b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,19 @@ # Upgrade to 3.0 +## BC BREAK: `Doctrine\ORM\Proxy\ProxyFactory` no longer extends abstract factory from `doctrine/common` + +It is no longer possible to call methods, constants or properties inherited +from that class on a `ProxyFactory` instance. + +`Doctrine\ORM\Proxy\ProxyFactory::createProxyDefinition()` and +`Doctrine\ORM\Proxy\ProxyFactory::resetUninitializedProxy()` are removed as well. + +## BC BREAK: lazy ghosts are enabled unconditionally + +`Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()` and +`Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()` are now no-ops and +will be deprecated in 3.1.0 + ## BC BREAK: collisions in identity map are unconditionally rejected `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` and diff --git a/composer.json b/composer.json index a87a9251146..2328a798c58 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "doctrine/lexer": "^2.1 || ^3", "doctrine/persistence": "^3.1.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^5.4 || ^6.0 || ^7.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "~6.2.13 || ^6.3.2" }, "require-dev": { "doctrine/coding-standard": "^12.0", @@ -43,7 +44,6 @@ "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4 || ^6.2", - "symfony/var-exporter": "^6.2.13", "vimeo/psalm": "5.15.0" }, "suggest": { diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 74ce79ec7c0..2bf45006d76 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -21,15 +21,12 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\Mapping\Driver\MappingDriver; -use Doctrine\Persistence\Reflection\RuntimeReflectionProperty; use LogicException; use Psr\Cache\CacheItemPoolInterface; -use Symfony\Component\VarExporter\LazyGhostTrait; use function class_exists; use function is_a; use function strtolower; -use function trait_exists; /** * Configuration container for all configuration options of Doctrine. @@ -581,28 +578,25 @@ public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void $this->attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses; } + /** + * To be deprecated in 3.1.0 + * + * @return true + */ public function isLazyGhostObjectEnabled(): bool { - return $this->attributes['isLazyGhostObjectEnabled'] ?? false; + return true; } + /** To be deprecated in 3.1.0 */ public function setLazyGhostObjectEnabled(bool $flag): void { - if ($flag && ! trait_exists(LazyGhostTrait::class)) { - throw new LogicException( - 'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library' - . ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".', - ); - } - - if ($flag && ! class_exists(RuntimeReflectionProperty::class)) { - throw new LogicException( - 'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library' - . ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".', - ); + if (! $flag) { + throw new LogicException(<<<'EXCEPTION' + The lazy ghost object feature cannot be disabled anymore. + Please remove the call to setLazyGhostObjectEnabled(false). + EXCEPTION); } - - $this->attributes['isLazyGhostObjectEnabled'] = $flag; } /** To be deprecated in 3.1.0 */ diff --git a/lib/Doctrine/ORM/Proxy/InternalProxy.php b/lib/Doctrine/ORM/Proxy/InternalProxy.php index baa5703f2a3..7c1d8339bd3 100644 --- a/lib/Doctrine/ORM/Proxy/InternalProxy.php +++ b/lib/Doctrine/ORM/Proxy/InternalProxy.php @@ -11,9 +11,8 @@ * * @template T of object * @template-extends Proxy - * - * @method void __setInitialized(bool $initialized) */ interface InternalProxy extends Proxy { + public function __setInitialized(bool $initialized): void; } diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 2a583488863..f04bd8c6625 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -5,11 +5,6 @@ namespace Doctrine\ORM\Proxy; use Closure; -use Doctrine\Common\Proxy\AbstractProxyFactory; -use Doctrine\Common\Proxy\Proxy as CommonProxy; -use Doctrine\Common\Proxy\ProxyDefinition; -use Doctrine\Common\Proxy\ProxyGenerator; -use Doctrine\Deprecations\Deprecation; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\ORMInvalidArgumentException; @@ -20,7 +15,6 @@ use Doctrine\Persistence\Proxy; use ReflectionProperty; use Symfony\Component\VarExporter\ProxyHelper; -use Throwable; use function array_combine; use function array_flip; @@ -51,12 +45,11 @@ use function ucfirst; use const DIRECTORY_SEPARATOR; -use const PHP_VERSION_ID; /** * This factory is used to create proxy objects for entities at runtime. */ -class ProxyFactory extends AbstractProxyFactory +class ProxyFactory { /** * Never autogenerate a proxy and rely that it was generated by some @@ -137,9 +130,6 @@ public function __serialize(): array /** @var array */ private $proxyFactories = []; - /** @var bool */ - private $isLazyGhostObjectEnabled = true; - /** * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. @@ -155,23 +145,6 @@ public function __construct( private readonly string $proxyNs, bool|int $autoGenerate = self::AUTOGENERATE_NEVER, ) { - if (! $em->getConfiguration()->isLazyGhostObjectEnabled()) { - if (PHP_VERSION_ID >= 80100) { - Deprecation::trigger( - 'doctrine/orm', - 'https://github.com/doctrine/orm/pull/10837/', - 'Not enabling lazy ghost objects is deprecated and will not be supported in Doctrine ORM 3.0. Ensure Doctrine\ORM\Configuration::setLazyGhostObjectEnabled(true) is called to enable them.', - ); - } - - $this->isLazyGhostObjectEnabled = false; - - $proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs); - $proxyGenerator->setPlaceholder('baseProxyInterface', CommonProxy::class . ', \\' . InternalProxy::class); - - parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate); - } - if (! $proxyDir) { throw ORMInvalidArgumentException::proxyDirectoryRequired(); } @@ -191,14 +164,13 @@ public function __construct( } /** - * {@inheritDoc} + * @param class-string $className + * @param array $identifier + * + * @return InternalProxy */ - public function getProxy($className, array $identifier) + public function getProxy(string $className, array $identifier) { - if (! $this->isLazyGhostObjectEnabled) { - return parent::getProxy($className, $identifier); - } - $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); return $proxyFactory($identifier); @@ -214,12 +186,8 @@ public function getProxy($className, array $identifier) * * @return int Number of generated proxies. */ - public function generateProxyClasses(array $classes, $proxyDir = null) + public function generateProxyClasses(array $classes, $proxyDir = null): int { - if (! $this->isLazyGhostObjectEnabled) { - return parent::generateProxyClasses($classes, $proxyDir); - } - $generated = 0; foreach ($classes as $class) { @@ -238,16 +206,6 @@ public function generateProxyClasses(array $classes, $proxyDir = null) return $generated; } - /** - * {@inheritDoc} - * - * @deprecated ProxyFactory::resetUninitializedProxy() is deprecated and will be removed in version 3.0 of doctrine/orm. - */ - public function resetUninitializedProxy(CommonProxy $proxy) - { - return parent::resetUninitializedProxy($proxy); - } - protected function skipClass(ClassMetadata $metadata): bool { return $metadata->isMappedSuperclass @@ -255,91 +213,6 @@ protected function skipClass(ClassMetadata $metadata): bool || $metadata->getReflectionClass()->isAbstract(); } - /** - * {@inheritDoc} - * - * @deprecated ProxyFactory::createProxyDefinition() is deprecated and will be removed in version 3.0 of doctrine/orm. - */ - protected function createProxyDefinition($className): ProxyDefinition - { - $classMetadata = $this->em->getClassMetadata($className); - $entityPersister = $this->uow->getEntityPersister($className); - - $initializer = $this->createInitializer($classMetadata, $entityPersister); - $cloner = $this->createCloner($classMetadata, $entityPersister); - - return new ProxyDefinition( - self::generateProxyClassName($className, $this->proxyNs), - $classMetadata->getIdentifierFieldNames(), - $classMetadata->getReflectionProperties(), - $initializer, - $cloner, - ); - } - - /** - * Creates a closure capable of initializing a proxy - * - * @deprecated ProxyFactory::createInitializer() is deprecated and will be removed in version 3.0 of doctrine/orm. - * - * @psalm-return Closure(CommonProxy):void - * - * @throws EntityNotFoundException - */ - private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure - { - $wakeupProxy = $classMetadata->getReflectionClass()->hasMethod('__wakeup'); - - return function (CommonProxy $proxy) use ($entityPersister, $classMetadata, $wakeupProxy): void { - $initializer = $proxy->__getInitializer(); - $cloner = $proxy->__getCloner(); - - $proxy->__setInitializer(null); - $proxy->__setCloner(null); - - if ($proxy->__isInitialized()) { - return; - } - - $properties = $proxy->__getLazyProperties(); - - foreach ($properties as $propertyName => $property) { - if (! isset($proxy->$propertyName)) { - $proxy->$propertyName = $properties[$propertyName]; - } - } - - $proxy->__setInitialized(true); - - if ($wakeupProxy) { - $proxy->__wakeup(); - } - - $identifier = $classMetadata->getIdentifierValues($proxy); - - try { - $entity = $entityPersister->loadById($identifier, $proxy); - } catch (Throwable $exception) { - $proxy->__setInitializer($initializer); - $proxy->__setCloner($cloner); - $proxy->__setInitialized(false); - - throw $exception; - } - - if ($entity === null) { - $proxy->__setInitializer($initializer); - $proxy->__setCloner($cloner); - $proxy->__setInitialized(false); - - throw EntityNotFoundException::fromClassNameAndIdentifier( - $classMetadata->getName(), - $this->identifierFlattener->flattenIdentifier($classMetadata, $identifier), - ); - } - }; - } - /** * Creates a closure capable of initializing a proxy * @@ -376,46 +249,6 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi }; } - /** - * Creates a closure capable of finalizing state a cloned proxy - * - * @deprecated ProxyFactory::createCloner() is deprecated and will be removed in version 3.0 of doctrine/orm. - * - * @psalm-return Closure(CommonProxy):void - * - * @throws EntityNotFoundException - */ - private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure - { - return function (CommonProxy $proxy) use ($entityPersister, $classMetadata): void { - if ($proxy->__isInitialized()) { - return; - } - - $proxy->__setInitialized(true); - $proxy->__setInitializer(null); - - $class = $entityPersister->getClassMetadata(); - $identifier = $classMetadata->getIdentifierValues($proxy); - $original = $entityPersister->loadById($identifier); - - if ($original === null) { - throw EntityNotFoundException::fromClassNameAndIdentifier( - $classMetadata->getName(), - $this->identifierFlattener->flattenIdentifier($classMetadata, $identifier), - ); - } - - foreach ($class->getReflectionProperties() as $property) { - if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) { - continue; - } - - $property->setValue($proxy, $property->getValue($original)); - } - }; - } - private function getProxyFileName(string $className, string $baseDirectory): string { $baseDirectory = $baseDirectory ?: $this->proxyDir; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 31b43e55fc0..e76366ac6fc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -190,11 +190,6 @@ parameters: count: 1 path: lib/Doctrine/ORM/Proxy/ProxyFactory.php - - - message: "#^Call to an undefined method Doctrine\\\\Common\\\\Proxy\\\\Proxy\\:\\:__wakeup\\(\\)\\.$#" - count: 1 - path: lib/Doctrine/ORM/Proxy/ProxyFactory.php - - message: "#^Call to an undefined static method Doctrine\\\\ORM\\\\Proxy\\\\ProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" count: 1 @@ -212,7 +207,7 @@ parameters: - message: "#^Parameter \\#1 \\$class of method Doctrine\\\\ORM\\\\Utility\\\\IdentifierFlattener\\:\\:flattenIdentifier\\(\\) expects Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata, Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata given\\.$#" - count: 3 + count: 1 path: lib/Doctrine/ORM/Proxy/ProxyFactory.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 6f4c80c5c96..6ea5468a0fa 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -738,16 +738,7 @@ $classMetadata - $classMetadata - $classMetadata - - createCloner - createInitializer - - - getReflectionProperties()]]> - Closure @@ -758,9 +749,6 @@ isEmbeddedClass]]> isMappedSuperclass]]> - - name]]> - proxyFactories[$className] = $proxyFactory]]> @@ -770,20 +758,10 @@ $i - - name]]> - - - getValue - setValue - 4]]> - - __wakeup - setProxyDir(__DIR__ . '/Proxies'); $configuration->setProxyNamespace('Doctrine\Tests\Proxies'); - - $proxyImplementation = getenv('ORM_PROXY_IMPLEMENTATION') - ?: (trait_exists(LazyGhostTrait::class) ? 'lazy-ghost' : 'common'); - - switch ($proxyImplementation) { - case 'lazy-ghost': - $configuration->setLazyGhostObjectEnabled(true); - - return; - - case 'common': - $configuration->setLazyGhostObjectEnabled(false); - - return; - } - - throw new LogicException(sprintf('Unknown proxy implementation: %s.', $proxyImplementation)); } private static function initializeDatabase(): void