0

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 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.

1 Answer 1

1

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' ]]]
Sign up to request clarification or add additional context in comments.

11 Comments

Tried this - new error message.... Fatal error: Uncaught MongoDB\Driver\Exception\BulkWriteException: Invalid $set :: caused by :: An object representing an expression must have exactly one field: { $arrayElemAt: { 1: "7", 2: "7", 3: "7", 4: "3", 5: "5", 6: "7", 7: "7", 8: "7", 9: "7" }, 0: "$$this" } in /var/www/html/vendor/mongodb/mongodb/src/Operation/Update.php:228
I had thought my $set variable was creating an array, as the first thing is an opening [ square brace?
The PHP driver uses associative arrays instead of objects, so where MongoDB expects an array of objects, you need to pass an array of associative arrays.
It looks like something is not serializing correctly. Try using print_r to output the exact content of $set before it is sent, and turn up the logging verbosity on mongod so you can see what it received.
My use of $set is the same as just writing out the syntax, following the documentation. A simple example provided there is: $updateResult = $collection->updateOne( [ 'restaurant_id' => '40356151' ], [ '$set' => [ 'name' => 'Brunos on Astoria' ]] ); . I am sure I'm following this syntax...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.