Skip to content
Advertisement

PHP updating multiple elements of sub-array of MongoDB document

I have the following document structure. I am trying to update specific values inside the holes sub-array:

round document

Each holes Array element is an Object representing the score on a golf hole, with various properties (fields). I am trying to provide the ability to update the holeGross field ONLY for each score. On my PHP website, this is using a POST form, which pushes an Array of scores and the round._id value too, as:

Array ( [holeScoreHidden] => Array ( [1] => 7 [2] => 7 [3] => 7 [4] => 8 [5] => 7 [6] => 7 [7] => 7 [8] => 7 [9] => 7 ) [roundId] => 60c642db09080f1b50331b2d [submit] => )

I have had some assistance on the MongoDB forums, with MongoDB-native (i.e. shell) syntax which I have tested in Compass and works:

[{$match: {
  _id: ObjectId('60c916684bd16901f36efb3a')
}}, {$set: {
  holes: {
    $map: {
      input: { $range: [0, { $size: "$holes"}]},
      in: {
        $mergeObjects: [
            { $arrayElemAt: ["$holes", "$$this"] },
            { holeGross: { $arrayElemAt: [[9,8,7,6,5,4,3,2,1], "$$this"] } }
          ]
      }
    }
  }
}}]

Using this as a basis, I tried converting to PHP-equivalent, as follows:

function setRoundScores($roundId, $scoresArray) {
        
    $collection = $client->golf->roundTest;
        
    $match = [ '_id' => new MongoDBBSONObjectID( $roundId ) ];
        
    $set = [
        '$set' => [
            'holes' => [
                '$map' => [
                    'input' => [
                        '$range' => [ 0, [ '$size' => '$holes']],
                        'in' => [
                            '$mergeObjects' => [
                                [ '$arrayElemAt' => [ '$holes', '$$this' ]],
                                [ 'holeGross' => [ '$arrayElemAt' => $scoresArray, '$$this' ]]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ];
        
    $updateOne = $collection->updateOne($match,$set);
        
    return $updateOne->getUpsertedId();
        
}

But this errors with:

Fatal error: Uncaught MongoDBDriverExceptionBulkWriteException: The dollar ($) prefixed field ‘$map’ in ‘holes.$map’ is not valid for storage. in /var/www/html/vendor/mongodb/mongodb/src/Operation/Update.php:228 Stack trace: #0 /var/www/html/vendor/mongodb/mongodb/src/Operation/Update.php(228): MongoDBDriverServer->executeBulkWrite(‘golf.roundTest’, Object(MongoDBDriverBulkWrite), Array) #1 /var/www/html/vendor/mongodb/mongodb/src/Operation/UpdateOne.php(117): MongoDBOperationUpdate->execute(Object(MongoDBDriverServer)) #2 /var/www/html/vendor/mongodb/mongodb/src/Collection.php(1075): MongoDBOperationUpdateOne->execute(Object(MongoDBDriverServer)) #3 /var/www/html/functions.php(803): MongoDBCollection->updateOne(Array, Array) #4 /var/www/html/updateRound.php(19): setRoundScores(’60cb07d14bd1690…’, Array) #5 {main} thrown in /var/www/html/vendor/mongodb/mongodb/src/Operation/Update.php on line 228

I am getting very confused as to the equivalent syntax, or even if the same methods are available in the PHP driver?

Can anyone point out the syntax issues / errors?

Finally, could I simply replace the entire holes sub-array, and provide that as a whole var to the updateOne() call? This would send much more data and is not the most efficient obviously…

*** UPDATE ***

@Joe was of great help in the final answer. @Joe provided a single line covering what was the cause of the issue.

For posterity, I thought to repost the final syntax of my function for others to be able to follow:

function setRoundScores($roundId, $scoresArray) {
        
    $client = new MongoDBClient($_ENV['MDB_CLIENT');

    $collection = $client->golf->round;

    $match = [ '_id' => new MongoDBBSONObjectID( $roundId ) ];
        
    $set = [
        '$set' => [
            'holes' => [
                '$map' => [
                    'input' => [
                        '$range' => [ 0, [ '$size' => '$holes']]
                    ],
                    'in' => [
                        '$mergeObjects' => [
                            [ '$arrayElemAt' => [ '$holes', '$$this' ]],
                            [ 'holeGross' => [ '$toInt' => [ '$arrayElemAt' => [ $scoresArray, '$$this' ]]]]
                        ]
                    ]
                        
                ]
            ]
        ]
    ];
        
    $updateOne = $collection->updateOne($match, [$set]);
        
    return $updateOne->getModifiedCount();
        
}

Of note, also needed to cast the $_POST array’s fields to Int’s as they are converted to Strings, so using the ‘$toInt’ method to perform this on the DB as well. Lots of extra square braces…

Also this is not an upsert so just returning the modified count instead.

Advertisement

Answer

The update part, the second argument to updateOne, must be an array in order to use aggregation operators.

Perhaps use

$updateOne = $collection->updateOne($match,[$set]);

Edit

There is a pair of brackets missing from the second arrayElemAt expression:

[ 'holeGross' => [ '$arrayElemAt' => $scoresArray, '$$this' ]]

should be

[ 'holeGross' => [ '$arrayElemAt' => [ $scoresArray, '$$this' ]]]
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement