Skip to content

Commit 8ba0249

Browse files
malarzmalcaeus
authored andcommitted
Prevent spl_object_hash collisions
1 parent bcb4f8f commit 8ba0249

File tree

2 files changed

+107
-4
lines changed

2 files changed

+107
-4
lines changed

lib/Doctrine/ODM/MongoDB/UnitOfWork.php

+14-4
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,21 @@ class UnitOfWork implements PropertyChangedListener
242242
private $persistenceBuilder;
243243

244244
/**
245-
* Array of parent associations between embedded documents
245+
* Array of parent associations between embedded documents.
246246
*
247-
* @todo We might need to clean up this array in clear(), doDetach(), etc.
248247
* @var array
249248
*/
250249
private $parentAssociations = array();
251250

251+
/**
252+
* Array of embedded documents known to UnitOfWork. We need to hold them to prevent spl_object_hash
253+
* collisions in case already managed object is lost due to GC (so now it won't). Embedded documents
254+
* found during doDetach are removed from the registry, to empty it altogether clear() can be utilized.
255+
*
256+
* @var array
257+
*/
258+
private $embeddedDocumentsRegistry = array();
259+
252260
/**
253261
* Initializes a new UnitOfWork instance, bound to the given DocumentManager.
254262
*
@@ -288,6 +296,7 @@ public function getPersistenceBuilder()
288296
public function setParentAssociation($document, $mapping, $parent, $propertyPath)
289297
{
290298
$oid = spl_object_hash($document);
299+
$this->embeddedDocumentsRegistry[$oid] = $document;
291300
$this->parentAssociations[$oid] = array($mapping, $parent, $propertyPath);
292301
}
293302

@@ -2185,7 +2194,7 @@ private function doDetach($document, array &$visited)
21852194
$this->documentDeletions[$oid], $this->documentIdentifiers[$oid],
21862195
$this->documentStates[$oid], $this->originalDocumentData[$oid],
21872196
$this->parentAssociations[$oid], $this->documentUpserts[$oid],
2188-
$this->hasScheduledCollections[$oid]);
2197+
$this->hasScheduledCollections[$oid], $this->embeddedDocumentsRegistry[$oid]);
21892198
break;
21902199
case self::STATE_NEW:
21912200
case self::STATE_DETACHED:
@@ -2488,7 +2497,8 @@ public function clear($documentName = null)
24882497
$this->collectionUpdates =
24892498
$this->collectionDeletions =
24902499
$this->parentAssociations =
2491-
$this->orphanRemovals =
2500+
$this->embeddedDocumentsRegistry =
2501+
$this->orphanRemovals =
24922502
$this->hasScheduledCollections = array();
24932503
} else {
24942504
$visited = array();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace Doctrine\ODM\MongoDB\Tests\Functional;
4+
5+
use Doctrine\ODM\MongoDB\DocumentManager;
6+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
7+
use Doctrine\ODM\MongoDB\Tests\BaseTest;
8+
9+
class SplObjectHashCollisionsTest extends BaseTest
10+
{
11+
/**
12+
* @dataProvider provideParentAssociationsIsCleared
13+
*/
14+
public function testParentAssociationsIsCleared($f)
15+
{
16+
$d = new SplColDoc();
17+
$d->one = new SplColEmbed('d.one.v1');
18+
$d->many[] = new SplColEmbed('d.many.0.v1');
19+
$d->many[] = new SplColEmbed('d.many.1.v1');
20+
21+
$this->dm->persist($d);
22+
$this->expectCount('parentAssociations', 3);
23+
$this->expectCount('embeddedDocumentsRegistry', 3);
24+
$f($this->dm, $d);
25+
$this->expectCount('parentAssociations', 0);
26+
$this->expectCount('embeddedDocumentsRegistry', 0);
27+
}
28+
29+
/**
30+
* @dataProvider provideParentAssociationsIsCleared
31+
*/
32+
public function testParentAssociationsLeftover($f, $leftover)
33+
{
34+
$d = new SplColDoc();
35+
$d->one = new SplColEmbed('d.one.v1');
36+
$d->many[] = new SplColEmbed('d.many.0.v1');
37+
$d->many[] = new SplColEmbed('d.many.1.v1');
38+
$this->dm->persist($d);
39+
$d->one = new SplColEmbed('d.one.v2');
40+
$this->dm->flush();
41+
42+
$this->expectCount('parentAssociations', 4);
43+
$this->expectCount('embeddedDocumentsRegistry', 4);
44+
$f($this->dm, $d);
45+
$this->expectCount('parentAssociations', $leftover);
46+
$this->expectCount('embeddedDocumentsRegistry', $leftover);
47+
}
48+
49+
public function provideParentAssociationsIsCleared()
50+
{
51+
return array(
52+
array( function (DocumentManager $dm) { $dm->clear(); }, 0 ),
53+
array( function (DocumentManager $dm, $doc) { $dm->clear(get_class($doc)); }, 1 ),
54+
array( function (DocumentManager $dm, $doc) { $dm->detach($doc); }, 1 ),
55+
);
56+
}
57+
58+
private function expectCount($prop, $expected)
59+
{
60+
$ro = new \ReflectionObject($this->uow);
61+
$rp = $ro->getProperty($prop);
62+
$rp->setAccessible(true);
63+
$this->assertCount($expected, $rp->getValue($this->uow));
64+
}
65+
}
66+
67+
/** @ODM\Document */
68+
class SplColDoc
69+
{
70+
/** @ODM\Id */
71+
public $id;
72+
73+
/** @ODM\Field(type="string") */
74+
public $name;
75+
76+
/** @ODM\EmbedOne */
77+
public $one;
78+
79+
/** @ODM\EmbedMany */
80+
public $many = array();
81+
}
82+
83+
/** @ODM\EmbeddedDocument */
84+
class SplColEmbed
85+
{
86+
/** @ODM\Field(type="string") */
87+
public $name;
88+
89+
public function __construct($name)
90+
{
91+
$this->name = $name;
92+
}
93+
}

0 commit comments

Comments
 (0)