バージョン1.17の新機能。
Overview
コーデックは、BSON ドキュメントを PHP オブジェクトにデコードし、PHP オブジェクトを BSON ドキュメントにエンコードするために使用されます。 対照的に、他のメソッド(例: タイプ マップ)のコーデックでは、さまざまなデータタイプのより大きなカスタマイズと処理が可能になります。 これらは BSON のエンコードとデコードのロジックをドメイン クラスから分離するため、BSON を普通の古い PHP オブジェクトにデコードすることもできます。
ドキュメントの取り扱い
メインのロジックはドキュメント コーデックに含まれています。 このクラスはMongoDB\Codec\DocumentCodecインターフェースを実装し、どのデータ型をエンコードまたはデコードできるか、またその方法を定義します。 次の例えでは、 Personクラスとそれを変換するためのコーデックを定義します。
use MongoDB\BSON\ObjectId;
final class Person{ public function __construct( public string $name, public readonly ObjectId $id = new ObjectId(), ) { }}
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オプションを指定します。
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とともに、または埋め込みドキュメントをデコードするときに使用することを目的としています。
コーデックでコレクションを使用する場合、コーデックは特定の操作でそのタイプのデータのみを受け入れて返します。 挿入および置換操作(例: insertOne 、 `findOneAndReplace 、および一部のbulkWrite操作は、提供されたコーデックを使用して指定されたデータをエンコードしようとします。 エンコードできないドキュメントを挿入または置換しようとすると、例外が発生します。 読み取り操作(例: aggregate 、 find 、 findOneAndUpdateは、提供されたコーデックを使用して返されたドキュメントの復号を試行します。 コーデックが返されたデータをサポートしていない場合、例外がスローされます。
任意の操作のcodecオプションにnullを指定することで、特定の操作のコーデックの使用を無効にしたり、別のコーデックを使用したりできます(たとえば、集計パイプラインの結果をデコードするためなど)。 あるいは、 typeMap操作を使用して 型マップ を指定すると、コレクション レベルのコーデックも上書きされます。
// 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インターフェースを実装することで実行できます。
次の例では、 BSON日付とそれに付随するタイムゾーンstringを含む埋め込みドキュメントとして、DateTimeInterface インスタンスを保存するコーデックを定義します。 これらは同じ埋め込みドキュメントを、BSON デコード中にDateTimeImmutableに変換することができます。
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フィールドを追加します。
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(), ) { }}最後に重要ですが、コーデックを変更して新しい フィールドを処理します。
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), ]); }}埋め込みドキュメントの処理
前の例では、単一のドキュメントを処理する方法を示しています。 ただし、埋め込みドキュメントを含むフィールドを処理したい場合もあります。 Personドキュメントに埋め込むAddressドキュメントを使用して、これを示します。 整合性を確保するために、これを読み取り専用クラスにします。
final readonly class Address{ public function __construct( public string $street, public string $postCode, public string $city, public string $country, ) { }}このクラスのドキュメント コーデックを作成できるようになりました。
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, ]); }}Personクラスには新しいaddressフィールドが取得されますが、これは任意のままにします。
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フィールドを処理できるようになりました。
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); }}