“文档” 菜单
文档首页
/ / /
PHP 库手册
/

编解码器

在此页面上

  • 概述
  • 处理文档
  • 处理字段和数据类型
  • 处理嵌入式文档

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->selectCollection('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操作)将尝试使用提供的编解码器对给定数据进行编码。 尝试插入或替换无法编码的文档将导致异常。 读取操作(例如 aggregatefindfindOneAndUpdate )将尝试使用提供的编解码器对返回的文档进行解码。 如果编解码器不支持返回的数据,则会抛出异常。

您可以通过为任何操作的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);
}
}

后退

增删改查操作

来年

排序规则