doctrine/mongodb-odm — pull request #524 by ihor (2013-03-07 13:37:53+00:00)
Title: Use $pullAll to remove elements from reference collections instead of $unset & $pull

I noticed a strange behavior in removing elements from reference collections. Is there any reason for using $unset & $pull instead of $pullAll for this?

Imagine a situation when some web request removes one reference. In case of high concurrency when two different requests come almost simultaneously and fetch the same state from the database this approach may lead to data corruption.

Look at this example. First request removes the first reference and second request removes the second reference accordingly.

```
> db.obj.insert({"refs": ["ref-0", "ref-1", "ref-2"]})
> db.obj.find()
{ "_id" : ObjectId("513885d4c24d1256ca090e10"), "refs" : [ "ref-0", "ref-1", "ref-2" ] }
> db.obj.update({"_id": ObjectId("513885d4c24d1256ca090e10")}, {$unset: {"refs.0": true}})
> db.obj.update({"_id": ObjectId("513885d4c24d1256ca090e10")}, {$pull: {"refs": null}})
> db.obj.update({"_id": ObjectId("513885d4c24d1256ca090e10")}, {$unset: {"refs.1": true}})
> db.obj.update({"_id": ObjectId("513885d4c24d1256ca090e10")}, {$pull: {"refs": null}})
> db.obj.find()
{ "_id" : ObjectId("513885d4c24d1256ca090e10"), "refs" : [ "ref-1" ] }
```

As result the data is corrupted. We still have the second reference but lost the third one.

As I understand $unset & $pull approach was used to be able to work with collection that may have identical values. But this doesn't make much sense for reference collections where all values should be unique. For example the following code also doesn't make much sense:

```php
/** @ODM\Document(collection="ref") */
class Ref {
/** @ODM\String */
protected $title;

public function __construct($title) {
$this->title = $title;
}
}

/** @ODM\Document(collection="obj") */
class Obj {
/** @ODM\ReferenceMany(targetDocument="Ref") */
protected $refs;

public function addRef(Ref $ref) {
$this->refs[] = $ref;
}

public function getRefs() {
return $this->refs;
}
}

$obj = new Obj();
$ref = new Ref('hello-world');
$obj->addRef($ref);
$obj->addRef($ref);

print_r($obj->getRefs());
```
```
Array
(
[0] => Ref Object
(
[title:protected] => hello-world
)

[1] => Ref Object
(
[title:protected] => hello-world
)
)
```
Technically references should be stored in sets or unique collections. In this case we can safely use $pullAll to remove them.

I implemented a temporary workaround which uses $pullAll to remove elements from reference collections and uses $unset & $pull approach for other collections. But I would like to discuss this case more.

Correct me if I made a mistake somewhere. Thanks.