doctrine/mongodb-odm — pull request #580 by spujadas (2013-05-12 16:03:31+00:00)
Title: fixed incomplete UnitOfWork::clear()

UnitOfWork's clear() method (called by DocumentManager's clear() method) does not reset UnitOfWork's $persisters attribute, which leads to an empty/zombie document being inserted in the repository if a (valid) object is persisted/flushed after a previous flush fails.

Here's some sample code showing the problem.

uniquefield = $string;
}

public function getId() {
return $this->id ;
}

}

// clean up first
$foo = $dm->getRepository('Foo')->findOneByUniquefield('bar');
if (!is_null($foo)) {
echo 'Removed previously existing document' . "\n";
$dm->remove($foo) ;
$dm->flush();
$dm->clear();
}

// repository should be empty
dump_repository($dm) ;

// create, persist and flush initial object
$foo1 = new Foo();
$foo1->setUniquefield('bar') ;
$dm->persist($foo1);
$dm->flush();
$dm->clear();

// create and persist second object with dup'd unique field
$foo2 = new Foo();
$foo2->setUniquefield('bar') ;
$dm->persist($foo2);

// flush fails
try {
$dm->flush();
}
catch (MongoCursorException $e) {
echo 'Expected exception as unique field is marked as unique' . "\n";
}

$dm->clear();

// remove initial object
$foo = $dm->getRepository('Foo')->findOneByUniquefield('bar');
$dm->remove($foo) ;
$dm->flush();
$dm->clear();

// an empty flush won't be a problem
$dm->flush();

// but now let's create a third object
$foo3 = new Foo();
$foo3->setUniquefield('foo') ; // could be anything else
$dm->persist($foo3);
$dm->flush();
$dm->clear();

/* repository should contain 1 object, actually contains 2, one of which is
* empty (only the id is populated) */
dump_repository($dm) ;

/* Running this code again will lead to:
MongoCursorException: localhost:27017: E11000 duplicate key error index: sandbox
.duptest.$uniquefield_1 dup key: { : null } in
…\doctrine-mongodb\lib\Doctrine\MongoDB\Collection.php on line 223
*/

function dump_repository(Doctrine\ODM\MongoDB\DocumentManager $dm) {
$foos = $dm->createQueryBuilder('Foo')->getQuery()->execute();
echo 'Repository contains ' . count($foos) . ' object(s):' . "\n" ;
foreach ($foos as $foo) {
var_dump($foo) ;
}
}
?>

This code yields (notice the first empty/zombie object Foo#46):
Repository contains 0 object(s):
Expected exception as unique field is marked as unique
Repository contains 2 object(s):
class Foo#46 (2) {
protected $id =>
string(24) "518fac5d101e3ce412000002"
protected $uniquefield =>
NULL
}
class Foo#55 (2) {
protected $id =>
string(24) "518fac5d101e3ce412000003"
protected $uniquefield =>
string(14) "foo"
}

Expected behaviour (after fix) is:
Repository contains 0 object(s):
Expected exception as unique field is marked as unique
Repository contains 1 object(s):
class Foo#46 (2) {
protected $id =>
string(24) "518fbc13101e3ca414000002"
protected $uniquefield =>
string(3) "foo"
}