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 MongoDB\BSON\ObjectID( $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 MongoDB\Driver\Exception\BulkWriteException: 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): MongoDB\Driver\Server->executeBulkWrite('golf.roundTest', Object(MongoDB\Driver\BulkWrite), Array) #1 /var/www/html/vendor/mongodb/mongodb/src/Operation/UpdateOne.php(117): MongoDB\Operation\Update->execute(Object(MongoDB\Driver\Server)) #2 /var/www/html/vendor/mongodb/mongodb/src/Collection.php(1075): MongoDB\Operation\UpdateOne->execute(Object(MongoDB\Driver\Server)) #3 /var/www/html/functions.php(803): MongoDB\Collection->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 MongoDB\Client($_ENV['MDB_CLIENT');
$collection = $client->golf->round;
$match = [ '_id' => new MongoDB\BSON\ObjectID( $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.
