Skip to content
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

Merged
merged 18 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/examples/codecs/handling-data-types/DateTimeCodec.php
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);
Copy link
Member

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?

Copy link
Member Author

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.

}

$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(),
]);
}
}
13 changes: 13 additions & 0 deletions docs/examples/codecs/handling-data-types/Person.php
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(),
) {
}
}
55 changes: 55 additions & 0 deletions docs/examples/codecs/handling-data-types/PersonCodec.php
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')),
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It duplicates the data in memory. Each Document and PackedArray instance is tied to its own bson_t* so we don't have to start refcounting the libmongoc BSON structures.

$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),
]);
}
}
12 changes: 12 additions & 0 deletions docs/examples/codecs/handling-documents/Person.php
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(),
) {
}
}
49 changes: 49 additions & 0 deletions docs/examples/codecs/handling-documents/PersonCodec.php
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,
]);
}
}
7 changes: 7 additions & 0 deletions docs/examples/codecs/handling-documents/disabling-codec.php
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']]);
13 changes: 13 additions & 0 deletions docs/examples/codecs/handling-documents/using-codec.php
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
12 changes: 12 additions & 0 deletions docs/examples/codecs/handling-embedded-documents/Address.php
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,
) {
}
}
56 changes: 56 additions & 0 deletions docs/examples/codecs/handling-embedded-documents/AddressCodec.php
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,
]);
}
}
14 changes: 14 additions & 0 deletions docs/examples/codecs/handling-embedded-documents/Person.php
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()
) {
}
}
67 changes: 67 additions & 0 deletions docs/examples/codecs/handling-embedded-documents/PersonCodec.php
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use MongoDB\Client;
use MongoDB\Driver\ClientEncryption;

require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../../vendor/autoload.php';

$uri = getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Exception\ServerException;

require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../../vendor/autoload.php';

$uri = getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Exception\ServerException;

require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../../vendor/autoload.php';

$uri = getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/';

Expand Down
Loading