mongo-php-library icon indicating copy to clipboard operation
mongo-php-library copied to clipboard

Command Monitoring Deserialization Issue with Nested Documents

Open BesedinSasha opened this issue 5 months ago • 1 comments

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.

BesedinSasha avatar Jul 10 '25 15:07 BesedinSasha