Menu Docs
Página inicial do Docs
/ / /
Manual da Biblioteca PHP
/

Codecs

Nesta página

  • Visão geral
  • Manipulação de documentos
  • Manipulação de campos e tipos de dados
  • Tratamento de documentos incorporados

Novidade na versão 1.17.

Os codecs são usados para decodificar documentos BSON em objetos PHP e codificar objetos PHP em documentos BSON. Em contraste com outros métodos (por exemplo, mapas de tipo), codecs permitem maior personalização e manuseio de diferentes tipos de dados. Eles separam a lógica para codificação e decodificação BSON das classes de domínio, o que também permite que o BSON seja decodificado em objetos PHP antigos e simples.

A lógica principal está contida em um codec de documento. Esta classe implementa a interface MongoDB\Codec\DocumentCodec e define quais tipos de dados podem ser codificados/decodificados e como. O exemplo a seguir define uma classe Person e um codec para transformá-la:

<?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,
]);
}
}

Para usar este codec com uma collection, especifique a opção codec ao selecionar a collection:

<?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();

O exemplo acima seleciona uma coleção e a instrui a usar o PersonCodec para codificar e decodificar documentos. Ao inserir dados, o PersonCodec é utilizado para codificar o documento. Ao recuperar dados, o mesmo PersonCodec é usado para decodificar dados BSON em uma instância Person . Observe que, embora o PersonCodec possa teoricamente decodificar qualquer documento BSON que contenha um campo de nome , não queremos usá-lo para nenhum outro documento. Os codecs de documentos devem ser usados com MongoDB\Collection ou ao decodificar documentos incorporados.

Ao usar uma collection com um codec, o codec só aceitará e retornará dados desse tipo para determinadas operações. Inserir e substituir operações (por exemplo insertOne, `findOneAndReplace e algumas operações bulkWrite ) tentarão codificar os dados fornecidos usando o codec fornecido. Tentar inserir ou substituir um documento que não pode ser codificado resultará em uma exceção. Operações de leitura (por exemplo aggregate, find e findOneAndUpdate) tentarão decodificar os documentos devolvidos usando o codec fornecido. Se o codec não suportar os dados retornados, será lançada uma exceção.

Você pode desativar o uso do codec para uma operação específica ou usar um codec diferente (por exemplo, para decodificar o resultado de um pipeline de agregação) especificando null para a opção codec para qualquer operação. Como alternativa, especificar um mapa de tipos usando a operação typeMap também substituirá o codec no nível da 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']]);

O exemplo anterior mostra como definir um codec para uma classe específica. No entanto, você pode querer criar um codec que lide com um tipo de dados específico em qualquer documento. Isto pode ser feito implementando a interface do MongoDB\Codec\Codec .

O exemplo a seguir define um codec que armazena instâncias DateTimeInterface como um documento incorporado contendo uma data BSON e a cadeia de caracteres de fuso horário que a acompanha. Esses mesmos documentos incorporados podem ser traduzidos de volta para um DateTimeImmutable durante a decodificação BSON.

<?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(),
]);
}
}

Observação

Ao escrever um codec, você deve ser o mais transitório possível no que se refere ao tratamento de dados. Nesse caso, o codec lida com qualquer DateTimeInterface ao codificar para BSON, pois uma instância UTCDateTime pode ser criada a partir de qualquer objeto desse tipo. Ao decodificar dados do BSON, ele sempre decodificará para uma instância DateTimeImmutable .

Este codec agora pode ser aproveitado por outros codecs que lidam com campos de data.

Primeiro, adicionamos um campo createdAt à classe Person :

<?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(),
) {
}
}

Por último, mas não menos importante, modificamos o codec para lidar com o novo campo:

<?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),
]);
}
}

Um exemplo anterior mostra como lidar com um único documento. No entanto, às vezes, você deseja lidar com campos que contêm documentos incorporados. Demonstraremos isso usando um documento Address , que incorporaremos dentro de um documento Person . Para garantir consistência, vamos fazer desta uma classe somente leitura:

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

Agora podemos criar um codec de documento para esta classe:

<?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,
]);
}
}

A classe Person obtém um novo campo address , mas deixaremos isso opcional:

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

O PersonCodec agora pode lidar com o campo address opcional ao transformar dados:

<?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);
}
}

Voltar

Tipos de dados personalizados