2
votes

I have a mongoDB document comprising the following structure:

{
        "_id" : ObjectId("537b9731fa4634134c8b45aa"),
        "kpis" : [
                {
                        "id" : 4,
                        "value" : 3.78,
                        "entered" : Timestamp(1401377656, 9)
                }
        ]
}

I want to remove ALL kpi documents where the id is x. This is quite simple to do on the database directly using the pull command:

db.lead.update({}, {$pull:{"kpis":{id:5}}}, {multi:true});

However my (several) attempts to match this syntax using the doctrine ODM have failed:

    $qb->update()
       ->field('kpis')
       ->pull($qb->expr()->field('kpis.id')->equals($kpi->getId()))
       ->multiple(true)
       ->getQuery()
       ->execute();

    // I've also tried..

    $qb->update()
       ->field('kpis')
       ->pull($qb->expr()->field('kpis')->elemMatch(
            $qb->expr()->field('kpis.id')->equals($kpi->getId())
        ))
       ->multiple(true)
       ->getQuery()
       ->execute();

Nothing is removed. Am I using the query builder correctly?

1

1 Answers

6
votes

I believe you want to do the following:

$qb->update()
    ->field('kpis')->pull(array('id' => $kpi->getId()))
    ->multiple(true)
    ->getQuery()
    ->execute();

You can use the Query::debug() method to dump the actual query generated by this. You should expect the following in the dumped newObj field:

{ "$pull": { "kpis": { "id": <number> }}}

To explain why your previous examples didn't work:

->field('kpis')
->pull($qb->expr()->field('kpis.id')->equals($kpi->getId()))

Here, you're creating the following query:

{ "$pull": { "kpis": { "kpis.id": <number> }}}

This would only match:

  1. If each embedded object in the top-level kpis array had an embedded kpis object within it, which in turn had an id field that matched the number.
  2. If the embedded objects in the top-level kpis array had their own kpis arrays consisting of embedded objects with an id field that matched. This would be MongoDB's array element matching coming into play.

In your second example, you have:

->field('kpis')
->pull($qb->expr()->field('kpis')->elemMatch(
    $qb->expr()->field('kpis.id')->equals($kpi->getId())
))

This would generate the following query:

{ "$pull": { "kpis": { "$elemMatch": { "kpis.id": <number> }}}}

I've never seen such syntax before, but I think the $elemMatch is superflous, since $pull already takes criteria to match an array element as its argument. $elemMatch is useful in a query when you're directly matching on an array field and don't mean to do a full equality match against the array elements (i.e. you want to provide criteria instead of an exact match).