Skip to content

Commit 4978e0e

Browse files
authored
Merge pull request #10819 from nicolas-grekas/fix-proxy-clone
2 parents 0b9060c + eee87c3 commit 4978e0e

File tree

4 files changed

+47
-42
lines changed

4 files changed

+47
-42
lines changed

lib/Doctrine/ORM/Proxy/ProxyFactory.php

+35-25
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ class <proxyShortClassName> extends \<className> implements \<baseProxyInterface
4848
{
4949
<useLazyGhostTrait>
5050
51-
/**
52-
* @internal
53-
*/
54-
public bool $__isCloning = false;
55-
56-
public function __construct(?\Closure $initializer = null)
51+
public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
5752
{
53+
if ($cloner !== null) {
54+
return;
55+
}
56+
5857
self::createLazyGhost($initializer, <skippedProperties>, $this);
5958
}
6059
@@ -63,17 +62,6 @@ public function __isInitialized(): bool
6362
return isset($this->lazyObjectState) && $this->isLazyObjectInitialized();
6463
}
6564
66-
public function __clone()
67-
{
68-
$this->__isCloning = true;
69-
70-
try {
71-
$this->__doClone();
72-
} finally {
73-
$this->__isCloning = false;
74-
}
75-
}
76-
7765
public function __serialize(): array
7866
{
7967
<serializeImpl>
@@ -98,6 +86,9 @@ public function __serialize(): array
9886
*/
9987
private $identifierFlattener;
10088

89+
/** @var ProxyDefinition[] */
90+
private $definitions = [];
91+
10192
/**
10293
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
10394
* connected to the given <tt>EntityManager</tt>.
@@ -131,6 +122,26 @@ public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $au
131122
$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());
132123
}
133124

125+
/**
126+
* {@inheritDoc}
127+
*/
128+
public function getProxy($className, array $identifier)
129+
{
130+
$proxy = parent::getProxy($className, $identifier);
131+
132+
if (! $this->em->getConfiguration()->isLazyGhostObjectEnabled()) {
133+
return $proxy;
134+
}
135+
136+
$initializer = $this->definitions[$className]->initializer;
137+
138+
$proxy->__construct(static function (Proxy $object) use ($initializer, $proxy): void {
139+
$initializer($object, $proxy);
140+
});
141+
142+
return $proxy;
143+
}
144+
134145
/**
135146
* {@inheritDoc}
136147
*/
@@ -158,7 +169,7 @@ protected function createProxyDefinition($className)
158169
$cloner = $this->createCloner($classMetadata, $entityPersister);
159170
}
160171

161-
return new ProxyDefinition(
172+
return $this->definitions[$className] = new ProxyDefinition(
162173
ClassUtils::generateProxyClassName($className, $this->proxyNs),
163174
$classMetadata->getIdentifierFieldNames(),
164175
$classMetadata->getReflectionProperties(),
@@ -231,15 +242,15 @@ private function createInitializer(ClassMetadata $classMetadata, EntityPersister
231242
/**
232243
* Creates a closure capable of initializing a proxy
233244
*
234-
* @return Closure(Proxy):void
245+
* @return Closure(Proxy, Proxy):void
235246
*
236247
* @throws EntityNotFoundException
237248
*/
238249
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure
239250
{
240-
return function (Proxy $proxy) use ($entityPersister, $classMetadata): void {
241-
$identifier = $classMetadata->getIdentifierValues($proxy);
242-
$entity = $entityPersister->loadById($identifier, $proxy->__isCloning ? null : $proxy);
251+
return function (Proxy $proxy, Proxy $original) use ($entityPersister, $classMetadata): void {
252+
$identifier = $classMetadata->getIdentifierValues($original);
253+
$entity = $entityPersister->loadById($identifier, $original);
243254

244255
if ($entity === null) {
245256
throw EntityNotFoundException::fromClassNameAndIdentifier(
@@ -248,7 +259,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
248259
);
249260
}
250261

251-
if (! $proxy->__isCloning) {
262+
if ($proxy === $original) {
252263
return;
253264
}
254265

@@ -315,15 +326,14 @@ private function generateUseLazyGhostTrait(ClassMetadata $class): string
315326
isLazyObjectInitialized as private;
316327
createLazyGhost as private;
317328
resetLazyObject as private;
318-
__clone as private __doClone;
319329
}'), $code);
320330

321331
return $code;
322332
}
323333

324334
private function generateSkippedProperties(ClassMetadata $class): string
325335
{
326-
$skippedProperties = ['__isCloning' => true];
336+
$skippedProperties = [];
327337
$identifiers = array_flip($class->getIdentifierFieldNames());
328338
$filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;
329339
$reflector = $class->getReflectionClass();

phpstan-baseline.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ parameters:
286286
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
287287

288288
-
289-
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Proxy\\:\\:\\$__isCloning\\.$#"
289+
message: "#^Call to an undefined method Doctrine\\\\Common\\\\Proxy\\\\Proxy\\:\\:__construct\\(\\)\\.$#"
290290
count: 1
291291
path: lib/Doctrine/ORM/Proxy/ProxyFactory.php
292292

psalm-baseline.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,11 @@
13921392
<code>$classMetadata</code>
13931393
<code>$classMetadata</code>
13941394
</ArgumentTypeCoercion>
1395+
<DirectConstructorCall>
1396+
<code><![CDATA[$proxy->__construct(static function (Proxy $object) use ($initializer, $proxy): void {
1397+
$initializer($object, $proxy);
1398+
})]]></code>
1399+
</DirectConstructorCall>
13951400
<InvalidArgument>
13961401
<code><![CDATA[$classMetadata->getReflectionProperties()]]></code>
13971402
<code><![CDATA[$em->getMetadataFactory()]]></code>
@@ -1400,7 +1405,6 @@
14001405
<NoInterfaceProperties>
14011406
<code><![CDATA[$metadata->isEmbeddedClass]]></code>
14021407
<code><![CDATA[$metadata->isMappedSuperclass]]></code>
1403-
<code><![CDATA[$proxy->__isCloning]]></code>
14041408
</NoInterfaceProperties>
14051409
<PossiblyNullPropertyFetch>
14061410
<code><![CDATA[$property->name]]></code>
@@ -1411,6 +1415,7 @@
14111415
<code>setAccessible</code>
14121416
</PossiblyNullReference>
14131417
<UndefinedInterfaceMethod>
1418+
<code>__construct</code>
14141419
<code>__wakeup</code>
14151420
</UndefinedInterfaceMethod>
14161421
</file>

tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php

+5-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
namespace Doctrine\Tests\ORM\Proxy;
66

77
use Doctrine\Common\EventManager;
8-
use Doctrine\Common\Proxy\Proxy as CommonProxy;
98
use Doctrine\DBAL\Connection;
109
use Doctrine\DBAL\Platforms\AbstractPlatform;
1110
use Doctrine\ORM\EntityNotFoundException;
@@ -227,21 +226,12 @@ public function testProxyClonesParentFields(): void
227226
->expects(self::atLeastOnce())
228227
->method('loadById');
229228

230-
if ($proxy instanceof CommonProxy) {
231-
$loadByIdMock->willReturn($companyEmployee);
229+
$loadByIdMock->willReturn($companyEmployee);
232230

233-
$persister
234-
->expects(self::atLeastOnce())
235-
->method('getClassMetadata')
236-
->willReturn($classMetaData);
237-
} else {
238-
$loadByIdMock->willReturnCallback(static function (array $id, CompanyEmployee $companyEmployee) {
239-
$companyEmployee->setSalary(1000); // A property on the CompanyEmployee
240-
$companyEmployee->setName('Bob'); // A property on the parent class, CompanyPerson
241-
242-
return $companyEmployee;
243-
});
244-
}
231+
$persister
232+
->expects(self::atLeastOnce())
233+
->method('getClassMetadata')
234+
->willReturn($classMetaData);
245235

246236
$cloned = clone $proxy;
247237
assert($cloned instanceof CompanyEmployee);

0 commit comments

Comments
 (0)