-
Notifications
You must be signed in to change notification settings - Fork 264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PHPLIB-1209: Add tutorial to show working with Codecs #1154
Changes from all commits
1beec47
6956785
f005bc6
ecd83ee
50ee3b8
028e7d4
d0714dc
b947b36
ae9f4eb
7228b88
b7aee6e
60ae286
b8a0fbc
c24e39b
d639a19
04ca6f7
e1f63ed
50a8be2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?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') | ||
jmikola marked this conversation as resolved.
Show resolved
Hide resolved
|
||
->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(), | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
use MongoDB\BSON\ObjectId; | ||
|
||
final class Person | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
public function __construct( | ||
public string $name, | ||
public readonly DateTime $createdAt = new DateTime(), | ||
public readonly ObjectId $id = new ObjectId(), | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?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')), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you get a nested BSON document, does it duplicates the data in memory or is it a reference to the parent data? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It duplicates the data in memory. Each |
||
$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), | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
use MongoDB\BSON\ObjectId; | ||
|
||
final class Person | ||
{ | ||
public function __construct( | ||
public string $name, | ||
public readonly ObjectId $id = new ObjectId(), | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?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, | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<?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']]); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?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(); | ||
alcaeus marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
final readonly class Address | ||
{ | ||
public function __construct( | ||
public string $street, | ||
public string $postCode, | ||
public string $city, | ||
public string $country, | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?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, | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
use MongoDB\BSON\ObjectId; | ||
|
||
final class Person | ||
{ | ||
public ?Address $address = null; | ||
|
||
public function __construct( | ||
public string $name, | ||
public readonly ObjectId $id = new ObjectId() | ||
) { | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you propose to add a method to the trait that wraps this generic 3 lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Will add in a separate PR.