コーデック
バージョン 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); } }