4

I want to update (replace) a subdocument within an array of all the documents, that have this specific embedded subdocument.

My sample content object is:

{
    "_id" : ObjectId("51f289e5345f9d10090022ef"),
    "title" : "This is a content",
    "descriptors" : [ 
        {
            "_id" : ObjectId("51f289e5345f9d10090022f4"),
            "name" : "This is a descriptor",
            "type" : "This is a property"
        },
        {
            "_id" : ObjectId("51f289e5345f9d10090022f0"),
            "name" : "This is another descriptor",
            "type" : "This is another property"
        }
    ]
}

I want to find the object with a specific descriptor._id. I want to replace the one subdocument with another one (respectivley with an updated version of the old object, but not necessarily with the same properties).

While this works well in RoboMongo / shell...

db.Content.update(
{
    'descriptors._id': ObjectId("51f289e5345f9d10090022f4")
},
{
    $set: {
        'descriptors.$': {
            "_id" : ObjectId("51f289e5345f9d10090022f4"),
            "name" : "This is the updated descriptor",
            "category" : "This is a new property"
        }
    }
},
{
    'multi': true
})

...and with the plain php methods...

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$mongo = new \Mongo('mongodb://localhost:27017');
$database = $mongo->selectDB('MyDatabase');

$output = $database->selectCollection('Content')->update(
    array('descriptors._id' => $descriptor['_id']),
    array('$set' => array('descriptors.$' => $descriptor)),
    array("multiple" => true)
);

...it doesn't work with Doctrine MongoDB ODM...

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id'])
->field('descriptors.$')->set($descriptor)
->getQuery()->execute();

...because it fails with the following error:

Notice: Undefined offset: 2 in C:\MyProject\vendor\doctrine\mongodb-odm\lib\Doctrine\ODM\MongoDB\Persisters\DocumentPersister.php line 998

So I assume, that Doctrine MongoDB ODM needs three parts in the dot-notation. The not so nice solution would be to iterate over the properties of the subdocument and set them manually.

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id']);
foreach ($descriptor as $key => $value) {
    $query->field('descriptors.$.'.$key)->set($value);
}
$query->getQuery()->execute();

But this will only update existing and add new properties, but won't remove old/unnecessary properties from the subdocument.

Any ideas how to solve the problem:

  • with a simple query
  • while using Doctrine MongoDB ODM
  • without looping over the subdocument-array in php

I'm using:

  • Mongo-Server: 2.4.5
  • PHP: 5.4.16
  • PHP-Mongo-Driver: 1.4.1

Composer:

"php": ">=5.3.3",
"symfony/symfony": "2.3.*",
"doctrine/orm": ">=2.2.3,<2.4-dev",
"doctrine/doctrine-bundle": "1.2.*",
"doctrine/mongodb-odm": "1.0.*@dev",
"doctrine/mongodb-odm-bundle": "3.0.*@dev"

2 Answers 2

2

This was a bug in ODM and should be fixed in PR #661. Please take a look and ensure that the revised test satisfies your use case.

Until the next beta of ODM is tagged, this will only reside in the master branch; however, that should match your 1.0.*@dev version requirement.

Sign up to request clarification or add additional context in comments.

Comments

1

You can do this thing but in a "not easy" way becuse when you use EmbedMany or ReferenceMany in Doctrine becames a Doctrine ArrayCollection objects that implemented the ArrayCollection contains() method with (as explained here) with the PHP in_array() method with the $strict parameter set to true... this way works with ReferenceMany but not with EmbedMany!

The concept is: ArrayCollection -> array -> ArrayCollection

In the definition of Document ...

/**
 * @ODM\EmbedMany(targetDocument="EmbeddedElement")
 */
private $elements = array();

...

public function getElements() { return $this->elements; }
public function setElements($elements) { $this->elements = $elements; }
public function addElement(Element $element) { $this->elements[] = $element; }

public function setElement(Element $element, $index=0)
{
    $elementsArray = $this->elements->toArray();

    $elementsArray[$index] = $element;

    $this->elements = new ArrayCollection($elementsArray);
}

public function removeElement(Element $element)
{
    $index = array_search($element, $this->elements->toArray(), $strict=false);

    if($index !== false)
        $this->elements->remove($index);
}

public function removeElementByIndex($index) { $this->elements->remove($index); }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.