Docs Menu
Docs Home
/ / /
PHP 라이브러리 매뉴얼
/

코덱

이 페이지의 내용

  • 개요
  • 문서 처리
  • 필드 및 데이터 유형 처리
  • 내장된 문서 처리

버전 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 연산)은 제공된 코덱을 사용하여 주어진 데이터를 인코딩하려고 시도합니다. 인코딩할 수 없는 문서를 삽입하거나 바꾸려고 하면 예외가 발생합니다. 읽기 작업(예: aggregate, findfindOneAndUpdate)은 제공된 코덱을 사용하여 반환된 문서 디코딩을 시도합니다. 코덱이 반환된 데이터를 지원하지 않으면 예외가 발생합니다.

특정 작업에 대한 코덱 사용을 비활성화하거나 작업의 codec 옵션에 null 를 지정하여 다른 코덱을 사용(예: 집계 파이프라인의 결과 디코딩)할 수 있습니다. 또는 typeMap 연산을 사용하여 유형 맵을 지정하면 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']]);

이전 예시에서는 특정 클래스에 대한 코덱을 정의하는 방법을 설명했습니다. 그러나 모든 문서에서 특정 데이터 유형을 처리하는 코덱을 만들 수 있습니다. 이는 MongoDB\Codec\Codec 인터페이스를 구현하여 달성할 수 있습니다.

다음 예시에서는 DateTimeInterface 인스턴스를 BSON 날짜 및 수반되는 시간대 문자열이 포함된 내장된 문서로 저장하는 코덱을 정의합니다. 그런 다음 동일한 내장된 문서를 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);
}
}

돌아가기

사용자 지정 데이터 유형