对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
Docs 菜单

使用类型编解码器对数据进行编码

1.17版本新增。

编解码器用于将 BSON 文档解码为 PHP 对象,以及将 PHP 对象编码为 BSON 文档。 与其他方法(例如 类型映射),编解码器允许更好地自定义和处理不同的数据类型。 它们将 BSON 编码和解码的逻辑与域类分开,这也使 BSON 能够解码为普通的旧 PHP 对象。

主要逻辑包含在文档编解码器中。 此类实现MongoDB\Codec\DocumentCodec接口并定义可以编码/解码哪些数据类型以及如何编码/解码。 以下示例定义了一个Person类和用于转换该类的编解码器:

<?php
use MongoDB\BSON\ObjectId;
final class Person
{
public function __construct(
public string $name,
public readonly ObjectId $id = new ObjectId(),
) {
}
}
<?php
use MongoDB\BSON\Document;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Exception\UnsupportedValueException;
/** @template-implements DocumentCodec<Person> */
final class PersonCodec implements DocumentCodec
{
// These traits define commonly used functionality to avoid duplication
use DecodeIfSupported;
use EncodeIfSupported;
public function canDecode(mixed $value): bool
{
return $value instanceof Document && $value->has('name');
}
public function canEncode(mixed $value): bool
{
return $value instanceof Person;
}
public function decode(mixed $value): Person
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}
return new Person(
$value->get('name'),
$value->get('_id'),
);
}
public function encode(mixed $value): Document
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}
return Document::fromPHP([
'_id' => $value->id,
'name' => $value->name,
]);
}
}

要将此编解码器用于集合,请在选择集合时指定codec选项:

<?php
use MongoDB\Client;
$client = new Client();
$collection = $client->getCollection('test', 'person', [
'codec' => new PersonCodec(),
]);
$person = new Person('Jane Doe');
$collection->insertOne($person);
$person = $collection->findOne();

上面的示例选择了一个集合,并指示其使用PersonCodec对文档进行编码和解码。 插入数据时,使用PersonCodec对文档进行编码。 检索数据时,使用相同的PersonCodec将 BSON 数据解码到Person实例中。 请注意,虽然从技术上讲, PersonCodec可以解码任何包含名称字段的 BSON 文档,但我们不想将其用于任何其他文档。 文档编解码器平均值与MongoDB\Collection一起使用,或在解码嵌入式文档时使用。

当使用一个collection与编解码器一起时,编解码器将仅为某些操作接受和返回该类型的数据。插入和替换操作(例如 insertOne 、 `findOneAndReplace和某些bulkWrite操作)将尝试使用提供的编解码器对给定数据进行编码。 尝试插入或替换无法编码的文档将导致异常。 读取操作(例如 aggregate 、 find和findOneAndUpdate )将尝试使用提供的编解码器对返回的文档进行解码。 如果编解码器不支持返回的数据,则会抛出异常。

您可以通过为任何操作的codec选项指定null ,禁用特定操作的编解码器使用或使用不同的编解码器(例如,解码聚合管道的结果)。 或者,使用typeMap操作指定类型映射也将覆盖collection级编解码器:

<?php
// Overrides the collection codec, falling back to the default type map
$collection->aggregate($pipeline, ['codec' => null]);
// Overrides the collection codec, using the specified type map
$collection->findOne($filter, ['typeMap' => ['root' => 'stdClass']]);

前面的示例展示了如何为特定类定义编解码器。 但是,您可能希望创建一个编解码器来处理任何文档中的特定数据类型。 这可以通过实施MongoDB\Codec\Codec接口来实现。

以下示例定义了一个编解码器,该编解码器将DateTimeInterface实例存储为嵌入式文档,其中包含 BSON 日期和随附的时区字符串。然后,这些相同的嵌入式文档可以在 BSON 解码期间被转换回DateTimeImmutable 。

<?php
use MongoDB\BSON\Document;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Codec\Codec;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Exception\UnsupportedValueException;
/** @template-implements Codec<Document, DateTimeImmutable> */
final class DateTimeCodec implements Codec
{
use DecodeIfSupported;
use EncodeIfSupported;
public function canDecode(mixed $value): bool
{
/* This codec inspects the BSON document to ensure it has the fields it expects, and that those fields are of
* the correct type. This is a robust approach to avoid decoding document that are not supported and would cause
* exceptions.
*
* For large documents, this can be inefficient as we're inspecting the entire document four times (once for
* each call to has() and get()). For small documents, this is not a problem.
*/
return $value instanceof Document
&& $value->has('utc') && $value->get('utc') instanceof UTCDateTime
&& $value->has('tz') && is_string($value->get('tz'));
}
public function canEncode(mixed $value): bool
{
return $value instanceof DateTimeInterface;
}
public function decode(mixed $value): DateTimeImmutable
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}
$timeZone = new DateTimeZone($value->get('tz'));
$dateTime = $value->get('utc')
->toDateTime()
->setTimeZone($timeZone);
return DateTimeImmutable::createFromMutable($dateTime);
}
public function encode(mixed $value): Document
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}
return Document::fromPHP([
'utc' => new UTCDateTime($value),
'tz' => $value->getTimezone()->getName(),
]);
}
}

注意

编写编解码器时,在处理数据方面应尽可能宽松。 在这种情况下,编解码器在编码为 BSON 时会处理任何DateTimeInterface ,因为可以从任何此类对象创建UTCDateTime实例。 从 BSON 解码数据时,它将始终解码为DateTimeImmutable实例。

现在,处理日期字段的其他编解码器可以利用此编解码器。

首先,我们向Person类添加一个createdAt字段:

<?php
use MongoDB\BSON\ObjectId;
final class Person
{
public function __construct(
public string $name,
public readonly DateTimeImmutable $createdAt = new DateTimeImmutable(),
public readonly ObjectId $id = new ObjectId(),
) {
}
}

最后但并非最不重要的一点是,我们修改编解码器以处理新字段:

<?php
use MongoDB\BSON\Document;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Exception\UnsupportedValueException;
/** @template-implements DocumentCodec<Person> */
final class PersonCodec implements DocumentCodec
{
use DecodeIfSupported;
use EncodeIfSupported;
public function __construct(
private readonly DateTimeCodec $dateTimeCodec = new DateTimeCodec(),
) {
}
public function canDecode(mixed $value): bool
{
return $value instanceof Document && $value->has('name');
}
public function canEncode(mixed $value): bool
{
return $value instanceof Person;
}
public function decode(mixed $value): Person
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}
return new Person(
$value->get('name'),
$this->dateTimeCodec->decode($value->get('createdAt')),
$value->get('_id'),
);
}
public function encode(mixed $value): Document
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}
return Document::fromPHP([
'_id' => $value->id,
'name' => $value->name,
'createdAt' => $this->dateTimeCodec->encode($value->createdAt),
]);
}
}

前面的示例展示了如何处理单个文档。 但是,有时您想要处理包含嵌入式文档的字段。我们将使用Address文档进行演示,并将其嵌入到Person文档中。 为了确保一致性,我们将其设为只读类:

<?php
final readonly class Address
{
public function __construct(
public string $street,
public string $postCode,
public string $city,
public string $country,
) {
}
}

我们现在可以为此类创建文档编解码器:

<?php
use MongoDB\BSON\Document;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Exception\UnsupportedValueException;
/** @template-implements DocumentCodec<Address> */
final class AddressCodec implements DocumentCodec
{
use DecodeIfSupported;
use EncodeIfSupported;
public function canDecode(mixed $value): bool
{
return $value instanceof Document
&& $value->has('street')
&& $value->has('postCode')
&& $value->has('city')
&& $value->has('country');
}
public function canEncode(mixed $value): bool
{
return $value instanceof Address;
}
public function decode(mixed $value): Address
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}
return new Address(
$value->get('street'),
$value->get('postCode'),
$value->get('city'),
$value->get('country'),
);
}
public function encode(mixed $value): Document
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}
return Document::fromPHP([
'street' => $value->street,
'postCode' => $value->postCode,
'city' => $value->city,
'country' => $value->country,
]);
}
}

Personaddress类获得一个新的字段,但我们将其保留为可选项:

<?php
use MongoDB\BSON\ObjectId;
final class Person
{
public ?Address $address = null;
public function __construct(
public string $name,
public readonly ObjectId $id = new ObjectId(),
) {
}
}

PersonCodec现在可以在转换数据时处理可选的address字段:

<?php
use MongoDB\BSON\Document;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Exception\UnsupportedValueException;
/** @template-implements DocumentCodec<Person> */
final class PersonCodec implements DocumentCodec
{
use DecodeIfSupported;
use EncodeIfSupported;
public function __construct(
private readonly AddressCodec $addressCodec = new AddressCodec(),
) {
}
public function canDecode(mixed $value): bool
{
return $value instanceof Document && $value->has('name');
}
public function canEncode(mixed $value): bool
{
return $value instanceof Person;
}
public function decode(mixed $value): Person
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}
$person = new Person(
$value->get('name'),
$value->get('_id'),
);
// Address is optional, so only decode if it exists
if ($value->has('address')) {
$person->address = $this->addressCodec->decode($value->get('address'));
}
return $person;
}
public function encode(mixed $value): Document
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}
$data = [
'_id' => $value->id,
'name' => $value->name,
];
// Don't add a null value to the document if address is not set
if ($value->address) {
$data['address'] = $this->addressCodec->encode($value->address);
}
return Document::fromPHP($data);
}
}