Skip to content

Command Monitoring Deserialization Issue with Nested Documents #1731

@BesedinSasha

Description

@BesedinSasha

When using MongoDB command monitoring (CommandSubscriber) with nested documents that implement Persistable interface, calling CommandStartedEvent::getCommand() after document insertion causes a fatal error due to deserialization issues.

Uncaught Error: Cannot use object of type stdClass as array

The issue occurs when:

  1. A document with nested objects is inserted into MongoDB
  2. The command monitoring subscriber attempts to access the command via CommandStartedEvent::getCommand()
  3. The MongoDB driver tries to deserialize the command data, but fails because nested documents are stored as stdClass objects instead of arrays

Environment

  • PHP 8.3.23
  • Library 2.1.0
  • Server 7.0.12 (standalone docker image, linux/arm64 platform)

mongodb
libbson bundled version => 1.30.5
libmongoc bundled version => 1.30.5
libmongoc SSL => enabled
libmongoc SSL library => OpenSSL
libmongoc crypto => enabled
libmongoc crypto library => libcrypto
libmongoc crypto system profile => disabled
libmongoc SASL => enabled
libmongoc SRV => enabled
libmongoc compression => enabled
libmongoc compression snappy => disabled
libmongoc compression zlib => enabled
libmongoc compression zstd => disabled
libmongocrypt bundled version => 1.12.0
libmongocrypt crypto => enabled
libmongocrypt crypto library => libcrypto
mongodb.debug => no value => no value

Installed modules

[PHP Modules]
amqp
apcu
bz2
Core
ctype
curl
date
dom
exif
fileinfo
filter
gd
gettext
hash
iconv
intl
json
libxml
mbstring
memcached
mongodb
mysqli
mysqlnd
openssl
opentelemetry
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
random
rdkafka
readline
redis
Reflection
session
SimpleXML
sockets
sodium
SPL
sqlite3
standard
tokenizer
xdebug
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]
Xdebug

Test Script

<?php

declare(strict_types=1);

use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\BSON\Persistable;
use MongoDB\Client;
use MongoDB\BSON\Unserializable;

require_once __DIR__ . '/vendor/autoload.php';

class NestedDocument
{
    public function __construct(public string $name)
    {
    }
}

class Document implements Persistable
{
    public function __construct(
        public int $id,
        public NestedDocument $nested,
    ) {
    }

    public function bsonSerialize(): array
    {
        return [
            'id' => $this->id,
            'nested' => ['name' => $this->nested->name],
        ];
    }

    public function bsonUnserialize(array $data): void
    {
        $this->id = $data['id'];
        $this->nested = new NestedDocument($data['nested']['name']);
    }
}

class CommandLogger implements CommandSubscriber
{
    private array $commands;

    public function __construct()
    {
        $this->commands = [];
    }

    public function commandStarted(CommandStartedEvent $event): void
    {
        $this->commands[$event->getRequestId()] = [
            'dbname' => $event->getDatabaseName(),
            'commandName' => $event->getCommandName(),
            'command' => $event->getCommand(), // This line causes the error
        ];
    }

    public function commandSucceeded(CommandSucceededEvent $event): void
    {
        unset($this->commands[$event->getRequestId()]);
    }

    public function commandFailed(CommandFailedEvent $event): void
    {
        unset($this->commands[$event->getRequestId()]);
    }
}

$client = new Client('mongodb://localhost:27017');
$client->addSubscriber(new CommandLogger());

$collection = $client->getDatabase('test')->getCollection('test');

// This works fine - no deserialization issues with queries
var_dump($collection->findOne([])); 

// This causes the error when CommandLogger tries to access getCommand()
var_dump($collection->insertOne(new Document(1, new NestedDocument('test name')))->getInsertedId());

Expected Behavior

The CommandStartedEvent::getCommand() method should return the command data without causing deserialization errors, even when the command involves documents with nested objects (Do not use bsonUnserialize() ?).

Actual Behavior

A fatal error occurs: Cannot use object of type stdClass as array when trying to access the command data for insert operations involving nested documents.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions