From d8dd5129e7e7fdde2658fd8c0d26246afa80c0b3 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 21 Feb 2013 01:14:11 +0100 Subject: [PATCH 1/2] Failing test for DDC-2306 As of DDC-1734, proxies should have null identifier when the UnitOFWork refreshes entities and replaces them (marking them as un-managed). The problem here is that refreshes on entities with same identifier but different type are still considered same, and the UnitOfWork discards the proxy instance instead of ignoring it. --- .../ORM/Functional/Ticket/DDC2306Test.php | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php new file mode 100644 index 000000000..6a4066c19 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php @@ -0,0 +1,151 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306Zone'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306User'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306Address'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306UserAddress'), + )); + } + + /** + * Verifies that when eager loading is triggered, proxies are kept managed. + * + * The problem resides in the refresh hint passed to {@see \Doctrine\ORM\UnitOfWork::createEntity}, + * which, as of DDC-1734, causes the proxy to be marked as un-managed. + * The check against the identity map only uses the identifier hash and the passed in class name, and + * does not take into account the fact that the set refresh hint may be for an entity of a different + * type from the one passed to {@see \Doctrine\ORM\UnitOfWork::createEntity} + * + * As a result, a refresh requested for an entity `Foo` with identifier `123` may cause a proxy + * of type `Bar` with identifier `123` to be marked as un-managed. + */ + public function testIssue() + { + $zone = new DDC2306Zone(); + $user = new DDC2306User; + $address = new DDC2306Address; + $userAddress = new DDC2306UserAddress($user, $address); + $user->zone = $zone; + $address->zone = $zone; + + $this->_em->persist($zone); + $this->_em->persist($user); + $this->_em->persist($address); + $this->_em->persist($userAddress); + $this->_em->flush(); + $this->_em->clear(); + + /* @var $address DDC2306Address */ + $address = $this->_em->find(__NAMESPACE__ . '\\DDC2306Address', $address->id); + /* @var $user DDC2306User|\Doctrine\ORM\Proxy\Proxy */ + $user = $address->users->first()->user; + + $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $user); + $this->assertInstanceOf(__NAMESPACE__ . '\\DDC2306User', $user); + + $userId = $user->id; + + $this->assertNotNull($userId); + + $user->__load(); + + $this->assertEquals( + $userId, + $user->id, + 'As of DDC-1734, the identifier is NULL for un-managed proxies. The identifier should be an integer here' + ); + } +} + +/** @Entity */ +class DDC2306Zone +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; +} + +/** + * @Entity + */ +class DDC2306User +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + + /** + * @var DDC2306UserAddress[]|\Doctrine\Common\Collections\Collection + * + * @OneToMany(targetEntity="DDC2306UserAddress", mappedBy="user") + */ + public $addresses; + + /** @ManyToOne(targetEntity="DDC2306Zone", fetch="EAGER") */ + public $zone; + + /** Constructor */ + public function __construct() { + $this->addresses = new ArrayCollection(); + } +} + +/** @Entity */ +class DDC2306Address +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + + /** + * @var DDC2306UserAddress[]|\Doctrine\Common\Collections\Collection + * + * @OneToMany(targetEntity="DDC2306UserAddress", mappedBy="address", orphanRemoval=true) + */ + public $users; + + /** @ManyToOne(targetEntity="DDC2306Zone", fetch="EAGER") */ + public $zone; + + /** Constructor */ + public function __construct() { + $this->users = new ArrayCollection(); + } +} + +/** @Entity */ +class DDC2306UserAddress +{ + /** @Id @Column(type="integer") @GeneratedValue */ + public $id; + + /** @ManyToOne(targetEntity="DDC2306User") */ + public $user; + + /** @ManyToOne(targetEntity="DDC2306Address", fetch="LAZY") */ + public $address; + + /** Constructor */ + public function __construct(DDC2306User $user, DDC2306Address $address) + { + $this->user = $user; + $this->address = $address; + + $user->addresses->add($this); + $address->users->add($this); + } +} \ No newline at end of file From a5ece5063ae7ef73a7fe1f0e2ff19707d9981975 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 21 Feb 2013 02:20:11 +0100 Subject: [PATCH 2/2] Fixing DDC-2306 --- lib/Doctrine/ORM/UnitOfWork.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 9848499d9..99fe9fb5f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2475,14 +2475,14 @@ class UnitOfWork implements PropertyChangedListener && isset($hints[Query::HINT_REFRESH_ENTITY]) && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity && $unmanagedProxy instanceof Proxy + && (($unmanagedProxyClass = $this->em->getClassMetadata(get_class($unmanagedProxy))) === $class) ) { // DDC-1238 - we have a managed instance, but it isn't the provided one. // Therefore we clear its identifier. Also, we must re-fetch metadata since the // refreshed object may be anything - $class = $this->em->getClassMetadata(get_class($unmanagedProxy)); - foreach ($class->identifier as $fieldName) { - $class->reflFields[$fieldName]->setValue($unmanagedProxy, null); + foreach ($unmanagedProxyClass->identifier as $fieldName) { + $unmanagedProxyClass->reflFields[$fieldName]->setValue($unmanagedProxy, null); } return $unmanagedProxy;