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-1180: Add basic infrastructure for codecs #1125

Merged
merged 16 commits into from
Jul 13, 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
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.
*/

Copy link
Member

@jmikola jmikola Jul 12, 2023

Choose a reason for hiding this comment

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

Don't forget to add copyright copypasta to all of the source files:

/*
 * 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.
 */

Not needed in tests and I think we can skip this for the stubs, too.

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)
Copy link
Member

Choose a reason for hiding this comment

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

Before we settle on this signature, I just want to confirm that you don't expect we'd ever need to solicit additional params in the constructor (e.g. options array).

If so, consider making this an array argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so - this particular library is a first-come-first-serve library. If we have the need for different behaviour, I'd rather create a separate library class for the purpose.

{
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;
jmikola marked this conversation as resolved.
Show resolved Hide resolved

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