Skip to content

Natively mapping BSON data types to wrapper classes or callbacks #900

Closed
@ralfstrobel

Description

@ralfstrobel

Issues #246 and #370 discussed difficulties decoding mongo dates to DateTime with specific time zones. Developer consensus favors userland conversion/wrapping as the most flexible solution. What I would like to propose in this issue is a performance optimization and generalization for this process.

Problem Description

Our application uses a self-developed database abstraction layer, which translates UTCDateTime objects to native PHP DateTime objects and vice versa, so that mongo specifics are not exposed to higher business logic. However, since this layer is unaware of document structure, this requires a naive iteration over all loaded and stored documents, effectively the following...

array_walk_recursive($document, function (&$value) {
    if ($value instanceof UTCDateTime) {
        $value = $value->toDateTime()->setTimezone($this->timezone);
    }
});

array_walk_recursive($document, function (&$value) {
    if ($value instanceof DateTime) {
        $value = new UTCDateTime($value);
    }
});

For large documents with sparse use of date values, this recursive search and replace leads to a very significant performance loss, with retrieval of the translated data taking up to 3x as long as native retrieval from mongo. Removing the content of the callback function reveals that this is not even caused by the actual conversion of UTCDateTime to DateTime, but mostly by the iteration and closure callback overhead.

Proposed solution

While deserializing from BSON, the driver naturally encounters all $date values and could directly convert them to a desired format (such as DateTime or a PHPC-640 wrapper), avoiding the need for subsequent traversal.

This approach would require extending the TypeMap syntax in a way that allows definitions for primitive BSON data types. Multiple variations are imaginable:

a) Mapping data types to a conversion callback:

[
    'document' => 'array',
    'fieldPaths' => [...],
    'date' => function (UTCDateTime $utcDate) use ($timezone) {
        return $utcDate->toDateTime()->setTimezone($timezone);
    }
]

b) Mapping data types to a custom wrapper class:

[
    'document' => 'array',
    'fieldPaths' => [...],
    'date' => 'MyDateTimeClass'
]

class MyDateTimeClass implements DateTimeInterface, \MongoDB\BSON\Serializable
{
    private $date;
    
    public function __construct(UTCDateTime $utcDate)
    {
        $this->date = $utcDate->toDateTime();
    }
    ...
}

While $date is likely the most common use case, this solution could certainly also be applied for other types such as $binary or $oid.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions