Skip to content

Commit

Permalink
Add CodecLibrary to keep track of multiple codecs
Browse files Browse the repository at this point in the history
  • Loading branch information
alcaeus committed Apr 12, 2023
1 parent 4f588db commit a436b81
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 0 deletions.
137 changes: 137 additions & 0 deletions src/Codec/CodecLibrary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

namespace MongoDB\Codec;

use MongoDB\Exception\UnexpectedValueException;

use function array_map;
use function get_debug_type;
use function sprintf;

class CodecLibrary implements Codec
{
/** @var array<Decoder> */
private $decoders = [];

/** @var array<Encoder> */
private $encoders = [];

/** @param Decoder|Encoder $items */
public function __construct(...$items)
{
array_map(
function ($item) {
if ($item instanceof Decoder) {
$this->attachDecoder($item);
}

if ($item instanceof Encoder) {
$this->attachEncoder($item);
}

// Yes, we'll silently discard everything. Please let me already have union types...
},
$items
);
}

/** @return static */
final public function attachCodec(Codec $codec): self
{
$this->decoders[] = $codec;
$this->encoders[] = $codec;
if ($codec instanceof KnowsCodecLibrary) {
$codec->attachLibrary($this);
}

return $this;
}

/** @return static */
final public function attachDecoder(Decoder $decoder): self
{
$this->decoders[] = $decoder;
if ($decoder instanceof KnowsCodecLibrary) {
$decoder->attachLibrary($this);
}

return $this;
}

/** @return static */
final public function attachEncoder(Encoder $encoder): self
{
$this->encoders[] = $encoder;
if ($encoder instanceof KnowsCodecLibrary) {
$encoder->attachLibrary($this);
}

return $this;
}

/** @param mixed $value */
final public function canDecode($value): bool
{
foreach ($this->decoders as $decoder) {
if ($decoder->canDecode($value)) {
return true;
}
}

return $value === null;
}

/** @param mixed $value */
final public function canEncode($value): bool
{
foreach ($this->encoders as $encoder) {
if ($encoder->canEncode($value)) {
return true;
}
}

return $value === null;
}

/**
* @param mixed $value
* @return mixed
*/
final public function decode($value)
{
foreach ($this->decoders as $decoder) {
if (! $decoder->canDecode($value)) {
continue;
}

return $decoder->decode($value);
}

if ($value === null) {
return null;
}

throw new UnexpectedValueException(sprintf('No decoder found for value of type "%s"', get_debug_type($value)));
}

/**
* @param mixed $value
* @return mixed
*/
final public function encode($value)
{
foreach ($this->encoders as $encoder) {
if (! $encoder->canEncode($value)) {
continue;
}

return $encoder->encode($value);
}

if ($value === null) {
return null;
}

throw new UnexpectedValueException(sprintf('No encoder found for value of type "%s"', get_debug_type($value)));
}
}
8 changes: 8 additions & 0 deletions src/Codec/KnowsCodecLibrary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace MongoDB\Codec;

interface KnowsCodecLibrary
{
public function attachLibrary(CodecLibrary $library): void;
}
142 changes: 142 additions & 0 deletions tests/Codec/CodecLibraryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace MongoDB\Tests\Codec;

use MongoDB\Codec\Codec;
use MongoDB\Codec\CodecLibrary;
use MongoDB\Codec\KnowsCodecLibrary;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Tests\TestCase;

class CodecLibraryTest extends TestCase
{
public function testDecode(): void
{
$codec = $this->getCodecLibrary();

$this->assertTrue($codec->canDecode('cigam'));
$this->assertFalse($codec->canDecode('magic'));

$this->assertSame('magic', $codec->decode('cigam'));
}

public function testDecodeNull(): void
{
$codec = $this->getCodecLibrary();

$this->assertTrue($codec->canDecode(null));
$this->assertNull($codec->decode(null));
}

public function testDecodeUnsupportedValue(): void
{
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('No decoder found for value of type "string"');

$this->getCodecLibrary()->decode('foo');
}

public function testEncode(): void
{
$codec = $this->getCodecLibrary();

$this->assertTrue($codec->canEncode('magic'));
$this->assertFalse($codec->canEncode('cigam'));

$this->assertSame('cigam', $codec->encode('magic'));
}

public function testEncodeNull(): void
{
$codec = $this->getCodecLibrary();

$this->assertTrue($codec->canEncode(null));
$this->assertNull($codec->encode(null));
}

public function testEncodeUnsupportedValue(): void
{
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('No encoder found for value of type "string"');

$this->getCodecLibrary()->encode('foo');
}

private function getCodecLibrary(): CodecLibrary
{
return new CodecLibrary(
/** @template-implements Codec<string, string> */
new class implements Codec
{
public function canDecode($value): bool
{
return $value === 'cigam';
}

public function canEncode($value): bool
{
return $value === 'magic';
}

public function decode($value)
{
return 'magic';
}

public function encode($value)
{
return 'cigam';
}
}
);
}

public function testLibraryAttachesToCodecs(): void
{
$codec = $this->getTestCodec();
$library = $this->getCodecLibrary();

$library->attachCodec($codec);
$this->assertSame($library, $codec->library);
}

public function testLibraryAttachesToCodecsWhenCreating(): void
{
$codec = $this->getTestCodec();
$library = new CodecLibrary($codec);

$this->assertSame($library, $codec->library);
}

private function getTestCodec(): Codec
{
return new class implements Codec, KnowsCodecLibrary {
public $library;

public function attachLibrary(CodecLibrary $library): void
{
$this->library = $library;
}

public function canDecode($value): bool
{
return false;
}

public function canEncode($value): bool
{
return false;
}

public function decode($value)
{
return null;
}

public function encode($value)
{
return null;
}
};
}
}

0 comments on commit a436b81

Please sign in to comment.