Search code examples
phpmongodbphp-mongodb

PHPMongo - Find and Returned only Matched embedded document in MongoDB


I am having trouble in returning matched embedded document using sokil PHPMongo ODM library. I am new to ODM concept and following is my document structure of collection Project:

{ 
    "_id" : ObjectId("59f889e46803fa3713454b5d"), 
    "projectName" : "usecase-updated", 
    "classes" : [
        {
            "_id" : ObjectId("59f9d7776803faea30b895dd"), 
            "className" : "OLA"
        }, 
        {
            "_id" : ObjectId("59f9d8ad6803fa4012b895df"), 
            "className" : "HELP"
        }, 
        {
            "_id" : ObjectId("59f9d9086803fa4112b895de"), 
            "className" : "DOC"
        }, 
        {
            "_id" : ObjectId("59f9d9186803fa4212b895de"), 
            "className" : "INVOC"
        }
    ]
}

So in my query first criteria is to get the Project with a matched ID and 2nd criteria is to to retrieve only the class from the classes array of embedded documents with a specific id.This is how i am building the query:

$collection = $PHPMongoDBInstance->getCollection("Project");



  $result = $collection->getDocument(
    "59f889e46803fa3713454b5d",
    function (\Sokil\Mongo\Cursor $cursor) {
        // get embedded documents with matched id
        $cursor->whereElemMatch("classes", $cursor->expression()->where("_id", new MongoId("59f9d7776803faea30b895dd")));

    }
);

I was expecting it to return only the embedded document of OLA from the usecase-updated document like this:

{ 
   "_id" : ObjectId("59f889e46803fa3713454b5d"), 
    "projectName" : "usecase-updated", 
    "classes" : [
          {
            "_id" : ObjectId("59f9d7776803faea30b895dd"), 
            "className" : "OLA"
          }
     ]
}

But PHPMongo library is returning the whole Project Document (shown in the start of question) with all the classes. Someone suggested to look into aggregation framework. But problem is there is not good enough documentation on the PHPMongo for using array aggregation functions (like $filter)

I tried it by using native instance of MongoCollection and with that i can use the findOne method to project my final result using this way:

$result = $collection->getMongoCollection("Project")->
             findOne(array("_id" => new MongoId("59f889e46803fa3713454b5d")), 
             array("classes" => 
                    array('$elemMatch' => 
                           array("_id" => new MongoId("59f9d7776803faea30b895dd")))));

If i want to achieve the similar projection using the getDocument method of sokil PHPMongo is there some possibility?

UPDATE: I tried achieving with aggregation framework and following was the query:

$result = $collection->aggregate(array(
    array(
        '$match' => array(
            "_id" => new MongoId("59f889e46803fa3713454b5d")
        )
    ),
    array(
        '$project' => array(
            'classes' => array(
                '$filter' => array(
                    'input' => '$classes',
                    'as' => 'classItem',
                    'cond' => array(
                        '$eq' => array('$$classItem._id' => new MongoId("59f9d7776803faea30b895dd"))
                    )
                )

            )

        )
    )

));

But i get this exception:

Sokil\\Mongo\\Exception\nMessage: Aggregate error: Unrecognized expression '$$classItem._id'\nFile:

Solution

  • So after posting the problem on the PHPMongo GitHub issues i found so far there is no implementation of $elemMatch. Author posted some interesting insights on this issue and now this issue is marked as enhancement for the future.

    Furthermore i have tried aggregation framework and i am succesfully able to achieve the desired results i found that there was a mistake in the syntax of following expression (for full query see above in the question):

    '$eq' => array('$$classItem._id' => new MongoId("59f9d7776803faea30b895dd"))
    

    I learned that expression of '$eq' shouldn't be a associative array rather each element in the expression should be a separate array item like this:

    '$eq' => array('$$class._id', new MongoId("59f9d7776803faea30b895dd"))
    

    So now my final aggregation query was like this:

    $collection = $PHPMongoDBInstance->getCollection("Project");
    $result = $collection->aggregate(array(
        array(
            '$match' => array(
                "_id" => new MongoId('59f889e46803fa3713454b5d')
            )
        ),
        array(
            '$project' => array(
                'classes' => array(
                    '$filter' => array(
                        'input' => '$classes',
                        'as' => 'class',
                        'cond' => array(
                            '$eq' => array('$$class._id', new MongoId("59f9d7776803faea30b895dd"))
                        )
                    )
                ),
                'projectName' => 1
            )
        )
    ));
    

    Hope this will help people in future if they had similar problem like me as i tried finding solution and it wasn't there over the internet. I will try to update once there is a feature enhancement in PHPMongo of $elemMatch.