Skip to content

Commit f3b6c4b

Browse files
authored
Use lazy ghosts unconditionally (#10969)
* Use lazy ghosts unconditionally * Stop extending proxy factory from doctrine/common Extending it no longer serves any purpose. * Transform annotation into actual method
1 parent 343afda commit f3b6c4b

File tree

9 files changed

+37
-253
lines changed

9 files changed

+37
-253
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ jobs:
3939
- "default"
4040
extension:
4141
- "pdo_sqlite"
42-
proxy:
43-
- "common"
4442
deps:
4543
- "highest"
4644
include:
@@ -53,10 +51,6 @@ jobs:
5351
- php-version: "8.2"
5452
dbal-version: "default"
5553
extension: "sqlite3"
56-
- php-version: "8.1"
57-
dbal-version: "default"
58-
proxy: "lazy-ghost"
59-
extension: "pdo_sqlite"
6054
- php-version: "8.1"
6155
dbal-version: "default"
6256
deps: "lowest"
@@ -90,13 +84,11 @@ jobs:
9084
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
9185
env:
9286
ENABLE_SECOND_LEVEL_CACHE: 0
93-
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
9487

9588
- name: "Run PHPUnit with Second Level Cache"
9689
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
9790
env:
9891
ENABLE_SECOND_LEVEL_CACHE: 1
99-
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
10092

10193
- name: "Upload coverage file"
10294
uses: "actions/upload-artifact@v3"

UPGRADE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Upgrade to 3.0
22

3+
## BC BREAK: `Doctrine\ORM\Proxy\ProxyFactory` no longer extends abstract factory from `doctrine/common`
4+
5+
It is no longer possible to call methods, constants or properties inherited
6+
from that class on a `ProxyFactory` instance.
7+
8+
`Doctrine\ORM\Proxy\ProxyFactory::createProxyDefinition()` and
9+
`Doctrine\ORM\Proxy\ProxyFactory::resetUninitializedProxy()` are removed as well.
10+
11+
## BC BREAK: lazy ghosts are enabled unconditionally
12+
13+
`Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()` and
14+
`Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()` are now no-ops and
15+
will be deprecated in 3.1.0
16+
317
## BC BREAK: collisions in identity map are unconditionally rejected
418

519
`Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` and

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"doctrine/lexer": "^2.1 || ^3",
3434
"doctrine/persistence": "^3.1.1",
3535
"psr/cache": "^1 || ^2 || ^3",
36-
"symfony/console": "^5.4 || ^6.0 || ^7.0"
36+
"symfony/console": "^5.4 || ^6.0 || ^7.0",
37+
"symfony/var-exporter": "~6.2.13 || ^6.3.2"
3738
},
3839
"require-dev": {
3940
"doctrine/coding-standard": "^12.0",
@@ -43,7 +44,6 @@
4344
"psr/log": "^1 || ^2 || ^3",
4445
"squizlabs/php_codesniffer": "3.7.2",
4546
"symfony/cache": "^5.4 || ^6.2",
46-
"symfony/var-exporter": "^6.2.13",
4747
"vimeo/psalm": "5.15.0"
4848
},
4949
"suggest": {

lib/Doctrine/ORM/Configuration.php

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@
2121
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
2222
use Doctrine\ORM\Repository\RepositoryFactory;
2323
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
24-
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
2524
use LogicException;
2625
use Psr\Cache\CacheItemPoolInterface;
27-
use Symfony\Component\VarExporter\LazyGhostTrait;
2826

2927
use function class_exists;
3028
use function is_a;
3129
use function strtolower;
32-
use function trait_exists;
3330

3431
/**
3532
* Configuration container for all configuration options of Doctrine.
@@ -581,28 +578,25 @@ public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void
581578
$this->attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
582579
}
583580

581+
/**
582+
* To be deprecated in 3.1.0
583+
*
584+
* @return true
585+
*/
584586
public function isLazyGhostObjectEnabled(): bool
585587
{
586-
return $this->attributes['isLazyGhostObjectEnabled'] ?? false;
588+
return true;
587589
}
588590

591+
/** To be deprecated in 3.1.0 */
589592
public function setLazyGhostObjectEnabled(bool $flag): void
590593
{
591-
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
592-
throw new LogicException(
593-
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
594-
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".',
595-
);
596-
}
597-
598-
if ($flag && ! class_exists(RuntimeReflectionProperty::class)) {
599-
throw new LogicException(
600-
'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library'
601-
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".',
602-
);
594+
if (! $flag) {
595+
throw new LogicException(<<<'EXCEPTION'
596+
The lazy ghost object feature cannot be disabled anymore.
597+
Please remove the call to setLazyGhostObjectEnabled(false).
598+
EXCEPTION);
603599
}
604-
605-
$this->attributes['isLazyGhostObjectEnabled'] = $flag;
606600
}
607601

608602
/** To be deprecated in 3.1.0 */

lib/Doctrine/ORM/Proxy/InternalProxy.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111
*
1212
* @template T of object
1313
* @template-extends Proxy<T>
14-
*
15-
* @method void __setInitialized(bool $initialized)
1614
*/
1715
interface InternalProxy extends Proxy
1816
{
17+
public function __setInitialized(bool $initialized): void;
1918
}

lib/Doctrine/ORM/Proxy/ProxyFactory.php

Lines changed: 7 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55
namespace Doctrine\ORM\Proxy;
66

77
use Closure;
8-
use Doctrine\Common\Proxy\AbstractProxyFactory;
9-
use Doctrine\Common\Proxy\Proxy as CommonProxy;
10-
use Doctrine\Common\Proxy\ProxyDefinition;
11-
use Doctrine\Common\Proxy\ProxyGenerator;
12-
use Doctrine\Deprecations\Deprecation;
138
use Doctrine\ORM\EntityManagerInterface;
149
use Doctrine\ORM\EntityNotFoundException;
1510
use Doctrine\ORM\ORMInvalidArgumentException;
@@ -20,7 +15,6 @@
2015
use Doctrine\Persistence\Proxy;
2116
use ReflectionProperty;
2217
use Symfony\Component\VarExporter\ProxyHelper;
23-
use Throwable;
2418

2519
use function array_combine;
2620
use function array_flip;
@@ -51,12 +45,11 @@
5145
use function ucfirst;
5246

5347
use const DIRECTORY_SEPARATOR;
54-
use const PHP_VERSION_ID;
5548

5649
/**
5750
* This factory is used to create proxy objects for entities at runtime.
5851
*/
59-
class ProxyFactory extends AbstractProxyFactory
52+
class ProxyFactory
6053
{
6154
/**
6255
* Never autogenerate a proxy and rely that it was generated by some
@@ -137,9 +130,6 @@ public function __serialize(): array
137130
/** @var array<class-string, Closure> */
138131
private $proxyFactories = [];
139132

140-
/** @var bool */
141-
private $isLazyGhostObjectEnabled = true;
142-
143133
/**
144134
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
145135
* connected to the given <tt>EntityManager</tt>.
@@ -155,23 +145,6 @@ public function __construct(
155145
private readonly string $proxyNs,
156146
bool|int $autoGenerate = self::AUTOGENERATE_NEVER,
157147
) {
158-
if (! $em->getConfiguration()->isLazyGhostObjectEnabled()) {
159-
if (PHP_VERSION_ID >= 80100) {
160-
Deprecation::trigger(
161-
'doctrine/orm',
162-
'https://github.com/doctrine/orm/pull/10837/',
163-
'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.',
164-
);
165-
}
166-
167-
$this->isLazyGhostObjectEnabled = false;
168-
169-
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);
170-
$proxyGenerator->setPlaceholder('baseProxyInterface', CommonProxy::class . ', \\' . InternalProxy::class);
171-
172-
parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate);
173-
}
174-
175148
if (! $proxyDir) {
176149
throw ORMInvalidArgumentException::proxyDirectoryRequired();
177150
}
@@ -191,14 +164,13 @@ public function __construct(
191164
}
192165

193166
/**
194-
* {@inheritDoc}
167+
* @param class-string $className
168+
* @param array<mixed> $identifier
169+
*
170+
* @return InternalProxy
195171
*/
196-
public function getProxy($className, array $identifier)
172+
public function getProxy(string $className, array $identifier)
197173
{
198-
if (! $this->isLazyGhostObjectEnabled) {
199-
return parent::getProxy($className, $identifier);
200-
}
201-
202174
$proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className);
203175

204176
return $proxyFactory($identifier);
@@ -214,12 +186,8 @@ public function getProxy($className, array $identifier)
214186
*
215187
* @return int Number of generated proxies.
216188
*/
217-
public function generateProxyClasses(array $classes, $proxyDir = null)
189+
public function generateProxyClasses(array $classes, $proxyDir = null): int
218190
{
219-
if (! $this->isLazyGhostObjectEnabled) {
220-
return parent::generateProxyClasses($classes, $proxyDir);
221-
}
222-
223191
$generated = 0;
224192

225193
foreach ($classes as $class) {
@@ -238,108 +206,13 @@ public function generateProxyClasses(array $classes, $proxyDir = null)
238206
return $generated;
239207
}
240208

241-
/**
242-
* {@inheritDoc}
243-
*
244-
* @deprecated ProxyFactory::resetUninitializedProxy() is deprecated and will be removed in version 3.0 of doctrine/orm.
245-
*/
246-
public function resetUninitializedProxy(CommonProxy $proxy)
247-
{
248-
return parent::resetUninitializedProxy($proxy);
249-
}
250-
251209
protected function skipClass(ClassMetadata $metadata): bool
252210
{
253211
return $metadata->isMappedSuperclass
254212
|| $metadata->isEmbeddedClass
255213
|| $metadata->getReflectionClass()->isAbstract();
256214
}
257215

258-
/**
259-
* {@inheritDoc}
260-
*
261-
* @deprecated ProxyFactory::createProxyDefinition() is deprecated and will be removed in version 3.0 of doctrine/orm.
262-
*/
263-
protected function createProxyDefinition($className): ProxyDefinition
264-
{
265-
$classMetadata = $this->em->getClassMetadata($className);
266-
$entityPersister = $this->uow->getEntityPersister($className);
267-
268-
$initializer = $this->createInitializer($classMetadata, $entityPersister);
269-
$cloner = $this->createCloner($classMetadata, $entityPersister);
270-
271-
return new ProxyDefinition(
272-
self::generateProxyClassName($className, $this->proxyNs),
273-
$classMetadata->getIdentifierFieldNames(),
274-
$classMetadata->getReflectionProperties(),
275-
$initializer,
276-
$cloner,
277-
);
278-
}
279-
280-
/**
281-
* Creates a closure capable of initializing a proxy
282-
*
283-
* @deprecated ProxyFactory::createInitializer() is deprecated and will be removed in version 3.0 of doctrine/orm.
284-
*
285-
* @psalm-return Closure(CommonProxy):void
286-
*
287-
* @throws EntityNotFoundException
288-
*/
289-
private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure
290-
{
291-
$wakeupProxy = $classMetadata->getReflectionClass()->hasMethod('__wakeup');
292-
293-
return function (CommonProxy $proxy) use ($entityPersister, $classMetadata, $wakeupProxy): void {
294-
$initializer = $proxy->__getInitializer();
295-
$cloner = $proxy->__getCloner();
296-
297-
$proxy->__setInitializer(null);
298-
$proxy->__setCloner(null);
299-
300-
if ($proxy->__isInitialized()) {
301-
return;
302-
}
303-
304-
$properties = $proxy->__getLazyProperties();
305-
306-
foreach ($properties as $propertyName => $property) {
307-
if (! isset($proxy->$propertyName)) {
308-
$proxy->$propertyName = $properties[$propertyName];
309-
}
310-
}
311-
312-
$proxy->__setInitialized(true);
313-
314-
if ($wakeupProxy) {
315-
$proxy->__wakeup();
316-
}
317-
318-
$identifier = $classMetadata->getIdentifierValues($proxy);
319-
320-
try {
321-
$entity = $entityPersister->loadById($identifier, $proxy);
322-
} catch (Throwable $exception) {
323-
$proxy->__setInitializer($initializer);
324-
$proxy->__setCloner($cloner);
325-
$proxy->__setInitialized(false);
326-
327-
throw $exception;
328-
}
329-
330-
if ($entity === null) {
331-
$proxy->__setInitializer($initializer);
332-
$proxy->__setCloner($cloner);
333-
$proxy->__setInitialized(false);
334-
335-
throw EntityNotFoundException::fromClassNameAndIdentifier(
336-
$classMetadata->getName(),
337-
$this->identifierFlattener->flattenIdentifier($classMetadata, $identifier),
338-
);
339-
}
340-
};
341-
}
342-
343216
/**
344217
* Creates a closure capable of initializing a proxy
345218
*
@@ -376,46 +249,6 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
376249
};
377250
}
378251

379-
/**
380-
* Creates a closure capable of finalizing state a cloned proxy
381-
*
382-
* @deprecated ProxyFactory::createCloner() is deprecated and will be removed in version 3.0 of doctrine/orm.
383-
*
384-
* @psalm-return Closure(CommonProxy):void
385-
*
386-
* @throws EntityNotFoundException
387-
*/
388-
private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure
389-
{
390-
return function (CommonProxy $proxy) use ($entityPersister, $classMetadata): void {
391-
if ($proxy->__isInitialized()) {
392-
return;
393-
}
394-
395-
$proxy->__setInitialized(true);
396-
$proxy->__setInitializer(null);
397-
398-
$class = $entityPersister->getClassMetadata();
399-
$identifier = $classMetadata->getIdentifierValues($proxy);
400-
$original = $entityPersister->loadById($identifier);
401-
402-
if ($original === null) {
403-
throw EntityNotFoundException::fromClassNameAndIdentifier(
404-
$classMetadata->getName(),
405-
$this->identifierFlattener->flattenIdentifier($classMetadata, $identifier),
406-
);
407-
}
408-
409-
foreach ($class->getReflectionProperties() as $property) {
410-
if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
411-
continue;
412-
}
413-
414-
$property->setValue($proxy, $property->getValue($original));
415-
}
416-
};
417-
}
418-
419252
private function getProxyFileName(string $className, string $baseDirectory): string
420253
{
421254
$baseDirectory = $baseDirectory ?: $this->proxyDir;

0 commit comments

Comments
 (0)