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-1181: Introduce lazy BSON classes #1135

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9064c9d
Add AsListIterator to remove gaps in arrays
alcaeus Jul 6, 2023
a214d58
Add lazy models for BSON documents and arrays
alcaeus Jul 6, 2023
f9fefe4
Introduce codecs to work with lazy BSON objects
alcaeus Jul 7, 2023
01fcf4b
Remove assert for keys and use get_object_vars when building LazyBSON…
alcaeus Jul 20, 2023
ffedb9a
Use null-coalesce assignments
alcaeus Jul 20, 2023
2b92818
Only support string offsets for LazyBSONDocument
alcaeus Jul 20, 2023
ddd75fe
Use object in LazyBSONCodecLibrary test
alcaeus Jul 20, 2023
18a5f2a
Remove useless doc comments
alcaeus Jul 20, 2023
11621f5
Make lazy BSON classes final
alcaeus Jul 20, 2023
3426ce1
Implement Countable in lazy BSON structures
alcaeus Jul 21, 2023
b08bd8e
Make lazy BSON classes serializable
alcaeus Jul 21, 2023
6708343
Implement JsonSerializable in lazy BSON classes
alcaeus Jul 21, 2023
74f48ae
Rename AsListIterator to ListIterator
alcaeus Jul 24, 2023
98c0313
Remove null-coalesce assignment
alcaeus Jul 26, 2023
b1bf347
Rename readFrom* methods for consistency
alcaeus Jul 26, 2023
8cb8d7c
Defer index update in ListIterator until after parent operation
alcaeus Jul 26, 2023
466473a
Use data provider for tests
alcaeus Jul 26, 2023
476080f
Improve assertions in lazy codec tests
alcaeus Jul 26, 2023
61c586d
Remove duplicate assertions
alcaeus Jul 26, 2023
21d5342
Clarify comment in tests
alcaeus Jul 26, 2023
2aad7f3
Rename KnowsCodecLibrary interface to CodecLibraryAware
alcaeus Jul 27, 2023
c3ab097
Allow lists with gap when building LazyBSONArrays
alcaeus Jul 31, 2023
8599960
Add clarifying comment when marking fields as existing
alcaeus Jul 31, 2023
82775ec
Use object in codec test
alcaeus Jul 31, 2023
9aa174d
Add comment explaining manual creation of nested lazy structures
alcaeus Jul 31, 2023
9f9254b
Ensure keys are sorted correctly in LazyBSONArray
alcaeus Aug 2, 2023
cf9af04
Address code review feedback
alcaeus Aug 2, 2023
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
73 changes: 73 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<code>($value is NativeType ? BSONType : $value)</code>
</MixedInferredReturnType>
</file>
<file src="src/Codec/LazyBSONArrayCodec.php">
<MixedAssignment>
<code>$return[]</code>
</MixedAssignment>
</file>
<file src="src/Command/ListCollections.php">
<MixedAssignment>
<code>$cmd[$option]</code>
Expand Down Expand Up @@ -168,6 +173,74 @@
<code><![CDATA[$this->index['name']]]></code>
</MixedReturnStatement>
</file>
<file src="src/Model/LazyBSONArray.php">
<MixedArgument>
<code>$offset</code>
<code>$offset</code>
<code>$offset</code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code><![CDATA[new CallbackFilterIterator(
$itemIterator,
/** @param TValue $value */
function ($value, int $offset) use (&$seen): bool {
return ! isset($this->unset[$offset]) && ! isset($seen[$offset]);
},
)]]></code>
<code><![CDATA[new CallbackIterator(
// Skip keys that were unset or handled in a previous iterator
new CallbackFilterIterator(
$itemIterator,
/** @param TValue $value */
function ($value, int $offset) use (&$seen): bool {
return ! isset($this->unset[$offset]) && ! isset($seen[$offset]);
},
),
/**
* @param TValue $value
* @return TValue
*/
function ($value, int $offset) use (&$seen) {
// Mark key as seen, skipping any future occurrences
$seen[$offset] = true;

// Return actual value (potentially overridden by offsetSet)
return $this->offsetGet($offset);
},
)]]></code>
</MixedArgumentTypeCoercion>
<MixedArrayAssignment>
<code>$seen[$offset]</code>
</MixedArrayAssignment>
<MixedAssignment>
<code>$value</code>
</MixedAssignment>
<RedundantConditionGivenDocblockType>
<code>is_array($input)</code>
</RedundantConditionGivenDocblockType>
<TypeDoesNotContainType>
<code>is_numeric($key)</code>
</TypeDoesNotContainType>
</file>
<file src="src/Model/LazyBSONDocument.php">
<MismatchingDocblockReturnType>
<code><![CDATA[Iterator<string, TValue>]]></code>
</MismatchingDocblockReturnType>
<MixedAssignment>
<code>$value</code>
</MixedAssignment>
<MixedInferredReturnType>
<code><![CDATA[Iterator<string, TValue>]]></code>
</MixedInferredReturnType>
<RedundantConditionGivenDocblockType>
<code>is_object($input)</code>
</RedundantConditionGivenDocblockType>
</file>
<file src="src/Model/ListIterator.php">
<InvalidTemplateParam>
<code>ListIterator</code>
</InvalidTemplateParam>
</file>
<file src="src/Operation/Aggregate.php">
<MixedArgument>
<code><![CDATA[$this->options['typeMap']]]></code>
Expand Down
111 changes: 111 additions & 0 deletions src/Codec/ArrayCodec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?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;

use function array_map;
use function is_array;

/**
* Codec to recursively encode/decode values in arrays.
*
* @template-implements Codec<array, array>
*/
final class ArrayCodec implements Codec, CodecLibraryAware
{
private ?CodecLibrary $library = null;

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

/**
* @param mixed $value
* @psalm-assert-if-true array $value
*/
public function canDecode($value): bool
{
return is_array($value);
}

/**
* @param mixed $value
* @psalm-assert-if-true array $value
*/
public function canEncode($value): bool
{
return is_array($value);
}

/** @param mixed $value */
public function decode($value): array
{
if (! $this->canDecode($value)) {
throw UnsupportedValueException::invalidDecodableValue($value);
}

return array_map(
[$this->getLibrary(), 'decodeIfSupported'],
$value,
);
}

/**
* @param mixed $value
* @return mixed
* @psalm-return ($value is array ? array : $value)
jmikola marked this conversation as resolved.
Show resolved Hide resolved
*/
public function decodeIfSupported($value)
{
return $this->canDecode($value) ? $this->decode($value) : $value;
}

/** @param mixed $value */
public function encode($value): array
{
if (! $this->canEncode($value)) {
throw UnsupportedValueException::invalidEncodableValue($value);
}

return array_map(
[$this->getLibrary(), 'encodeIfSupported'],
$value,
);
}

/**
* @param mixed $value
* @return mixed
* @psalm-return ($value is array ? array : $value)
*/
public function encodeIfSupported($value)
{
return $this->canEncode($value) ? $this->encode($value) : $value;
}

private function getLibrary(): CodecLibrary
{
if (! $this->library) {
$this->library = new CodecLibrary();
}

return $this->library;
}
}
6 changes: 3 additions & 3 deletions src/Codec/CodecLibrary.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final public function attachCodec(Codec $codec): self
{
$this->decoders[] = $codec;
$this->encoders[] = $codec;
if ($codec instanceof KnowsCodecLibrary) {
if ($codec instanceof CodecLibraryAware) {
$codec->attachCodecLibrary($this);
}

Expand All @@ -75,7 +75,7 @@ final public function attachCodec(Codec $codec): self
final public function attachDecoder(Decoder $decoder): self
{
$this->decoders[] = $decoder;
if ($decoder instanceof KnowsCodecLibrary) {
if ($decoder instanceof CodecLibraryAware) {
$decoder->attachCodecLibrary($this);
}

Expand All @@ -86,7 +86,7 @@ final public function attachDecoder(Decoder $decoder): self
final public function attachEncoder(Encoder $encoder): self
{
$this->encoders[] = $encoder;
if ($encoder instanceof KnowsCodecLibrary) {
if ($encoder instanceof CodecLibraryAware) {
$encoder->attachCodecLibrary($this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* it was added to. The library will be injected when the codec is added to the
* library. This allows codecs to recursively encode its nested values.
*/
interface KnowsCodecLibrary
interface CodecLibraryAware
{
public function attachCodecLibrary(CodecLibrary $library): void;
}
110 changes: 110 additions & 0 deletions src/Codec/LazyBSONArrayCodec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?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\PackedArray;
use MongoDB\Exception\UnsupportedValueException;
use MongoDB\Model\LazyBSONArray;

/**
* Codec for lazy decoding of BSON PackedArray instances
*
* @template-implements Codec<PackedArray, LazyBSONArray>
*/
final class LazyBSONArrayCodec implements Codec, CodecLibraryAware
{
private ?CodecLibrary $library = null;

public function attachCodecLibrary(CodecLibrary $library): void
{
$this->library = $library;
alcaeus marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @param mixed $value
* @psalm-assert-if-true PackedArray $value
*/
public function canDecode($value): bool
{
return $value instanceof PackedArray;
}

/**
* @param mixed $value
* @psalm-assert-if-true LazyBSONArray $value
*/
public function canEncode($value): bool
{
return $value instanceof LazyBSONArray;
}

/** @param mixed $value */
public function decode($value): LazyBSONArray
{
if (! $value instanceof PackedArray) {
throw UnsupportedValueException::invalidDecodableValue($value);
}

return new LazyBSONArray($value, $this->getLibrary());
}

/**
* @param mixed $value
* @return mixed
* @psalm-return ($value is PackedArray ? LazyBSONArray : $value)
*/
public function decodeIfSupported($value)
{
return $this->canDecode($value) ? $this->decode($value) : $value;
}

/** @param mixed $value */
public function encode($value): PackedArray
{
if (! $value instanceof LazyBSONArray) {
throw UnsupportedValueException::invalidEncodableValue($value);
}

$return = [];
/** @var mixed $offsetValue */
foreach ($value as $offsetValue) {
$return[] = $this->getLibrary()->encodeIfSupported($offsetValue);
jmikola marked this conversation as resolved.
Show resolved Hide resolved
}

return PackedArray::fromPHP($return);
jmikola marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @param mixed $value
* @return mixed
* @psalm-return ($value is LazyBSONArray ? PackedArray : $value)
*/
public function encodeIfSupported($value)
{
return $this->canEncode($value) ? $this->encode($value) : $value;
}

private function getLibrary(): CodecLibrary
{
if (! $this->library) {
$this->library = new LazyBSONCodecLibrary();
}

return $this->library;
}
}
31 changes: 31 additions & 0 deletions src/Codec/LazyBSONCodecLibrary.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;

final class LazyBSONCodecLibrary extends CodecLibrary
{
public function __construct()
{
parent::__construct(
new LazyBSONDocumentCodec(),
new LazyBSONArrayCodec(),
new ArrayCodec(),
new ObjectCodec(),
jmikola marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
Loading