I have the following document structure. I am trying to update specific values inside the holes sub-array:
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' ]]]