Docs Menu
Docs Home
/ / /
PHP ライブラリ マニュアル
/

コーデック

項目一覧

  • Overview
  • ドキュメントの取り扱い
  • フィールドとデータ型の処理
  • 埋め込みドキュメントの処理

バージョン 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とともに、または埋め込みドキュメントをデコードするときに使用することを目的としています。

コーデックでコレクションを使用する場合、コーデックは特定の操作でそのタイプのデータのみを受け入れて返します。 挿入および置換操作(例: insertOne`findOneAndReplace 、および一部のbulkWrite操作は、提供されたコーデックを使用して指定されたデータをエンコードしようとします。 エンコードできないドキュメントを挿入または置換しようとすると、例外が発生します。 読み取り操作(例: aggregatefindfindOneAndUpdateは、提供されたコーデックを使用して返されたドキュメントの復号を試行します。 コーデックが返されたデータをサポートしていない場合、例外がスローされます。

任意の操作のcodecオプションにnullを指定することで、特定の操作のコーデックの使用を無効にしたり、別のコーデックを使用したりできます(たとえば、集計パイプラインの結果をデコードするためなど)。 あるいは、 typeMap操作を使用して 型マップ を指定すると、コレクション レベルのコーデックも上書きされます。

<?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インターフェースを実装することで実行できます。

次の例では、 BSON日付とそれに付随するタイムゾーンstringを含む埋め込みドキュメントとして、DateTimeInterface インスタンスを保存するコーデックを定義します。 これらは同じ埋め込みドキュメントを、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),
]);
}
}

前の例では、単一のドキュメントを処理する方法を示しています。 ただし、埋め込みドキュメントを含むフィールドを処理したい場合もあります。 Personドキュメントに埋め込むAddressドキュメントを使用して、これを示します。 整合性を確保するために、これを読み取り専用クラスにします。

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

Personクラスには新しいaddressフィールドが取得されますが、これは任意のままにします。

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

戻る

カスタムデータ型