코덱
이 페이지의 내용
버전 1.17에 추가 되었습니다.
개요
코덱은 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
연산을 사용하여 유형 맵을 지정하면 collection 수준 코덱도 재정의됩니다.
// 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
로 다시 변환할 수 있습니다.
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); } }