Working with MongoDB in PHP can be confusing, especially when it comes to the behavior of findOne() and how the results can be accessed. This blog post aims to clarify the nuances and help you avoid common pitfalls.
findOne() in PHPThe findOne() method retrieves a single document from a MongoDB collection. By default, it returns a MongoDB\Model\BSONDocument object. This object supports:
->): Access document properties like $doc->key.['key']): Access properties like $doc['key'], because BSONDocument implements PHP's ArrayAccess interface.Here's a simple example:
$doc = $collection->findOne([
'slug' => 'john-doe',
]);
if ($doc) {
// Object-style access
echo "Slug (object-style): " . $doc->slug . "\n";
// Array-style access
echo "Slug (array-style): " . $doc['slug'] . "\n";
} else {
echo "No document found with slug 'john-doe'.\n";
}Both access styles work fine in this case. But what happens if you call jsonSerialize() or use the typeMap option?
jsonSerialize()Calling jsonSerialize() on a BSONDocument converts it into a stdClass object, which only supports object-style access (->). Array-style access (['key']) will fail.
Here’s an example:
$serializedDoc = $doc->jsonSerialize();
// Object-style access works
echo "Slug (object-style): " . $serializedDoc->slug . "\n";
// Array-style access will fail
try {
echo "Slug (array-style): " . $serializedDoc['slug'] . "\n";
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
}If the document exists:
Slug (object-style): john-doe
Error: Cannot use object of type stdClass as arrayThis demonstrates that jsonSerialize() changes the access behavior.
typeMapYou can use the typeMap option to control how findOne() returns the document. For example:
$doc = $collection->findOne([
'slug' => 'john-doe',
], [
'typeMap' => ['root' => 'array'],
]);
if ($doc) {
// Array-style access works
echo "Slug (array-style): " . $doc['slug'] . "\n";
// Object-style access will fail
try {
echo "Slug (object-style): " . $doc->slug . "\n";
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}$doc = $collection->findOne([
'slug' => 'john-doe',
], [
'typeMap' => ['root' => 'object'],
]);
if ($doc) {
// Object-style access works
echo "Slug (object-style): " . $doc->slug . "\n";
// Array-style access will fail
try {
echo "Slug (array-style): " . $doc['slug'] . "\n";
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}findOne() returns a BSONDocument, supporting both access styles.jsonSerialize(): Converts to stdClass, breaking array-style access.typeMap:['root' => 'array']: Returns an associative array, breaking object-style access.['root' => 'object']: Returns a PHP object, breaking array-style access.MongoDB’s PHP driver offers flexibility in accessing documents, but it can be confusing due to the interplay of BSONDocument, jsonSerialize(), and typeMap. Here’s a quick summary:
| Access Type | Default (BSONDocument) | After jsonSerialize() | With typeMap => array | With typeMap => object |
|---|---|---|---|---|
Object-style (->) | Works | Works | Fails | Works |
Array-style ([]) | Works | Fails | Works | Fails |
When working with findOne(), choose the method that best suits your needs and stick to it for consistency. Let me know if this post helps clarify the confusion!