Skip to content

Commit

Permalink
PHPLIB-1180: Add basic infrastructure for codecs (#1125)
Browse files Browse the repository at this point in the history
* Add psalm stubs for new BSON classes

* Add basic codec infrastructure

* Introduce codec library

* Exclude methods inherited from traits in PedantryTest

This commit also uses dataset names for better visibility during debugging

* Exclude stubs from git exports

* Improve architecture document

* Use native types for DocumentCodec interface

* Add description to codec interfaces

* Remove internal designation from Decoder and Encoder interfaces

* Remove unnecessary psalm-param annotations

* Rename attachLibrary method in KnowsCodecLibrary interface

The new method name is more specific and helps prevent naming conflicts

* Use better values in test codecs

* Add comment explaining stub files are temporary

* Add comment explaining potential mocking when using PHPUnit 10

* Introduce domain exceptions for decoding and encoding

* Improve codec architecture documentation
  • Loading branch information
alcaeus committed Jul 13, 2023
1 parent 794bde5 commit 3e2e42f
Show file tree
Hide file tree
Showing 18 changed files with 904 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ tests export-ignore
docs export-ignore
examples export-ignore
mongo-orchestration export-ignore
stubs export-ignore
tools export-ignore
Makefile export-ignore
phpcs.xml.dist export-ignore
Expand Down
10 changes: 10 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@
<code>$mergedDriver['platform']</code>
</MixedAssignment>
</file>
<file src="src/Codec/DecodeIfSupported.php">
<MixedInferredReturnType occurrences="1">
<code>($value is BSONType ? NativeType : $value)</code>
</MixedInferredReturnType>
</file>
<file src="src/Codec/EncodeIfSupported.php">
<MixedInferredReturnType occurrences="1">
<code>($value is NativeType ? BSONType : $value)</code>
</MixedInferredReturnType>
</file>
<file src="src/Command/ListCollections.php">
<MixedAssignment occurrences="2">
<code>$cmd[$option]</code>
Expand Down
5 changes: 5 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<stubs>
<file name="stubs/BSON/Document.stub.php"/>
<file name="stubs/BSON/Iterator.stub.php"/>
<file name="stubs/BSON/PackedArray.stub.php"/>
</stubs>
</psalm>
31 changes: 31 additions & 0 deletions src/Codec/Codec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/*
* Copyright 2023-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace MongoDB\Codec;

/**
* The Codec interface allows decoding BSON data to native PHP types and back
* to BSON.
*
* @psalm-template BSONType
* @psalm-template NativeType
* @template-extends Decoder<BSONType, NativeType>
* @template-extends Encoder<BSONType, NativeType>
*/
interface Codec extends Decoder, Encoder
{
}
146 changes: 146 additions & 0 deletions src/Codec/CodecLibrary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
/*
* Copyright 2023-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace MongoDB\Codec;

use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedValueException;

class CodecLibrary implements Codec
{
use DecodeIfSupported;
use EncodeIfSupported;

/** @var array<Decoder> */
private $decoders = [];

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

/** @param Decoder|Encoder $items */
public function __construct(...$items)
{
foreach ($items as $item) {
if (! $item instanceof Decoder && ! $item instanceof Encoder) {
throw InvalidArgumentException::invalidType('$items', $item, [Decoder::class, Encoder::class]);
}

if ($item instanceof Codec) {
// Use attachCodec to avoid multiple calls to attachLibrary
$this->attachCodec($item);

continue;
}

if ($item instanceof Decoder) {
$this->attachDecoder($item);
}

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

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

return $this;
}

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

return $this;
}

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

return $this;
}

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

return false;
}

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

return false;
}

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

throw UnsupportedValueException::invalidDecodableValue($value);
}

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

throw UnsupportedValueException::invalidEncodableValue($value);
}
}
52 changes: 52 additions & 0 deletions src/Codec/DecodeIfSupported.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
/*
* Copyright 2023-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace MongoDB\Codec;

use MongoDB\Exception\UnsupportedValueException;

/**
* @psalm-template BSONType
* @psalm-template NativeType
*/
trait DecodeIfSupported
{
/**
* @param mixed $value
* @psalm-assert-if-true BSONType $value
*/
abstract public function canDecode($value): bool;

/**
* @param mixed $value
* @psalm-param BSONType $value
* @return mixed
* @psalm-return NativeType
* @throws UnsupportedValueException if the decoder does not support the value
*/
abstract public function decode($value);

/**
* @param mixed $value
* @return mixed
* @psalm-return ($value is BSONType ? NativeType : $value)
*/
public function decodeIfSupported($value)
{
return $this->canDecode($value) ? $this->decode($value) : $value;
}
}
59 changes: 59 additions & 0 deletions src/Codec/Decoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/*
* Copyright 2023-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace MongoDB\Codec;

use MongoDB\Exception\UnsupportedValueException;

/**
* @psalm-template BSONType
* @psalm-template NativeType
*/
interface Decoder
{
/**
* Checks if the decoder supports a given value.
*
* @param mixed $value
* @psalm-assert-if-true BSONType $value
*/
public function canDecode($value): bool;

/**
* Decodes a given value. If the decoder does not support the value, it
* should throw an exception.
*
* @param mixed $value
* @psalm-param BSONType $value
* @return mixed
* @psalm-return NativeType
* @throws UnsupportedValueException if the decoder does not support the value
*/
public function decode($value);

/**
* Decodes a given value if supported, otherwise returns the value as-is.
*
* The DecodeIfSupported trait provides a default implementation of this
* method.
*
* @param mixed $value
* @return mixed
* @psalm-return ($value is BSONType ? NativeType : $value)
*/
public function decodeIfSupported($value);
}
46 changes: 46 additions & 0 deletions src/Codec/DocumentCodec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
/*
* Copyright 2023-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace MongoDB\Codec;

use MongoDB\BSON\Document;
use MongoDB\Exception\UnsupportedValueException;

/**
* The DocumentCodec interface allows decoding BSON document data to native PHP
* objects and back to BSON documents.
*
* @psalm-template ObjectType of object
* @template-extends Codec<Document, ObjectType>
*/
interface DocumentCodec extends Codec
{
/**
* @param mixed $value
* @psalm-param Document $value
* @psalm-return ObjectType
* @throws UnsupportedValueException if the decoder does not support the value
*/
public function decode($value): object;

/**
* @param mixed $value
* @psalm-param ObjectType $value
* @throws UnsupportedValueException if the encoder does not support the value
*/
public function encode($value): Document;
}
Loading

0 comments on commit 3e2e42f

Please sign in to comment.