Skip to content

Commit 20cc649

Browse files
Fix cloning entities
1 parent 1a5a4c6 commit 20cc649

File tree

5 files changed

+89
-8
lines changed

5 files changed

+89
-8
lines changed

psalm-baseline.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,9 @@
15021502
<code><![CDATA[__wakeup]]></code>
15031503
</UndefinedInterfaceMethod>
15041504
<UndefinedMethod>
1505-
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
1505+
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
1506+
$initializer($object, $identifier);
1507+
}, $skippedProperties)]]></code>
15061508
</UndefinedMethod>
15071509
<UnresolvableInclude>
15081510
<code><![CDATA[require $fileName]]></code>

src/Proxy/ProxyFactory.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,15 +354,14 @@ private function createInitializer(ClassMetadata $classMetadata, EntityPersister
354354
/**
355355
* Creates a closure capable of initializing a proxy
356356
*
357-
* @return Closure(InternalProxy, InternalProxy):void
357+
* @return Closure(InternalProxy, array):void
358358
*
359359
* @throws EntityNotFoundException
360360
*/
361361
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
362362
{
363-
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
364-
$identifier = $classMetadata->getIdentifierValues($proxy);
365-
$original = $entityPersister->loadById($identifier);
363+
return static function (InternalProxy $proxy, array $identifier) use ($entityPersister, $classMetadata, $identifierFlattener): void {
364+
$original = $entityPersister->loadById($identifier);
366365

367366
if ($original === null) {
368367
throw EntityNotFoundException::fromClassNameAndIdentifier(
@@ -378,7 +377,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
378377
$class = $entityPersister->getClassMetadata();
379378

380379
foreach ($class->getReflectionProperties() as $property) {
381-
if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
380+
if (isset($identifier[$property->name]) || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
382381
continue;
383382
}
384383

@@ -468,7 +467,9 @@ private function getProxyFactory(string $className): Closure
468467
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
469468

470469
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
471-
$proxy = self::createLazyGhost($initializer, $skippedProperties);
470+
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void {
471+
$initializer($object, $identifier);
472+
}, $skippedProperties);
472473

473474
foreach ($identifierFields as $idField => $reflector) {
474475
if (! isset($identifier[$idField])) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\ECommerce;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\ORM\Mapping\Column;
10+
use Doctrine\ORM\Mapping\Entity;
11+
use Doctrine\ORM\Mapping\GeneratedValue;
12+
use Doctrine\ORM\Mapping\Id;
13+
use Doctrine\ORM\Mapping\Index;
14+
use Doctrine\ORM\Mapping\JoinColumn;
15+
use Doctrine\ORM\Mapping\JoinTable;
16+
use Doctrine\ORM\Mapping\ManyToMany;
17+
use Doctrine\ORM\Mapping\OneToMany;
18+
use Doctrine\ORM\Mapping\OneToOne;
19+
use Doctrine\ORM\Mapping\Table;
20+
21+
/**
22+
* ECommerceProduct2
23+
* Resets the id when being cloned.
24+
*
25+
* @Entity
26+
* @Table(name="ecommerce_products",indexes={@Index(name="name_idx", columns={"name"})})
27+
*/
28+
class ECommerceProduct2
29+
{
30+
/**
31+
* @var int|null
32+
* @Column(type="integer")
33+
* @Id
34+
* @GeneratedValue
35+
*/
36+
private $id;
37+
38+
/**
39+
* @var string
40+
* @Column(type="string", length=50, nullable=true)
41+
*/
42+
private $name;
43+
44+
public function getId(): ?int
45+
{
46+
return $this->id;
47+
}
48+
49+
public function getName(): string
50+
{
51+
return $this->name;
52+
}
53+
54+
public function __clone()
55+
{
56+
$this->id = null;
57+
$this->name = 'Clone of ' . $this->name;
58+
}
59+
}

tests/Tests/ORM/Functional/ProxiesLikeEntitiesTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ protected function setUp(): void
5858
public function testPersistUpdate(): void
5959
{
6060
// Considering case (a)
61-
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => 123]);
61+
$proxy = $this->_em->getProxyFactory()->getProxy(CmsUser::class, ['id' => $this->user->getId()]);
6262

6363
$proxy->id = null;
6464
$proxy->username = 'ocra';

tests/Tests/ORM/Functional/ReferenceProxyTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Doctrine\ORM\Proxy\InternalProxy;
1010
use Doctrine\Tests\Models\Company\CompanyAuction;
1111
use Doctrine\Tests\Models\ECommerce\ECommerceProduct;
12+
use Doctrine\Tests\Models\ECommerce\ECommerceProduct2;
1213
use Doctrine\Tests\Models\ECommerce\ECommerceShipping;
1314
use Doctrine\Tests\OrmFunctionalTestCase;
1415

@@ -112,6 +113,24 @@ public function testCloneProxy(): void
112113
self::assertFalse($entity->isCloned);
113114
}
114115

116+
public function testCloneProxyWithResetId(): void
117+
{
118+
$id = $this->createProduct();
119+
120+
$entity = $this->_em->getReference(ECommerceProduct2::class, $id);
121+
assert($entity instanceof ECommerceProduct2);
122+
123+
$clone = clone $entity;
124+
assert($clone instanceof ECommerceProduct2);
125+
126+
self::assertEquals($id, $entity->getId());
127+
self::assertEquals('Doctrine Cookbook', $entity->getName());
128+
129+
self::assertFalse($this->_em->contains($clone));
130+
self::assertNull($clone->getId());
131+
self::assertEquals('Clone of Doctrine Cookbook', $clone->getName());
132+
}
133+
115134
/** @group DDC-733 */
116135
public function testInitializeProxy(): void
117136
{

0 commit comments

Comments
 (0)