Skip to content

Commit cf3a5ea

Browse files
committed
Add array and object codecs
These codecs iterate over an array or stdClass instance and handle all of its values
1 parent c581c3f commit cf3a5ea

File tree

6 files changed

+421
-0
lines changed

6 files changed

+421
-0
lines changed

psalm-baseline.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,33 @@
2323
</MixedAssignment>
2424
</file>
2525
<file src="src/Codec/DecodeIfSupported.php">
26+
<InvalidReturnType occurrences="1">
27+
<code>($value is BSONType ? NativeType : $value)</code>
28+
</InvalidReturnType>
29+
<MixedArgumentTypeCoercion occurrences="1">
30+
<code>$value</code>
31+
</MixedArgumentTypeCoercion>
2632
<MixedInferredReturnType occurrences="1">
2733
<code>($value is BSONType ? NativeType : $value)</code>
2834
</MixedInferredReturnType>
2935
</file>
3036
<file src="src/Codec/EncodeIfSupported.php">
37+
<InvalidReturnType occurrences="1">
38+
<code>($value is NativeType ? BSONType : $value)</code>
39+
</InvalidReturnType>
40+
<MixedArgumentTypeCoercion occurrences="1">
41+
<code>$value</code>
42+
</MixedArgumentTypeCoercion>
3143
<MixedInferredReturnType occurrences="1">
3244
<code>($value is NativeType ? BSONType : $value)</code>
3345
</MixedInferredReturnType>
3446
</file>
47+
<file src="src/Codec/ObjectCodec.php">
48+
<RawObjectIteration occurrences="2">
49+
<code>$value</code>
50+
<code>$value</code>
51+
</RawObjectIteration>
52+
</file>
3553
<file src="src/Command/ListCollections.php">
3654
<MixedAssignment occurrences="2">
3755
<code>$cmd[$option]</code>

src/Codec/ArrayCodec.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace MongoDB\Codec;
4+
5+
use MongoDB\Exception\InvalidArgumentException;
6+
7+
use function array_map;
8+
use function is_array;
9+
10+
/**
11+
* Codec to recursively encode/decode values in arrays.
12+
*
13+
* @template-implements Codec<array, array>
14+
*/
15+
class ArrayCodec implements Codec, KnowsCodecLibrary
16+
{
17+
/** @template-use DecodeIfSupported<array, array> */
18+
use DecodeIfSupported;
19+
/** @template-use EncodeIfSupported<array, array> */
20+
use EncodeIfSupported;
21+
22+
/** @var CodecLibrary|null */
23+
private $library = null;
24+
25+
public function attachLibrary(CodecLibrary $library): void
26+
{
27+
$this->library = $library;
28+
}
29+
30+
/** @inheritDoc */
31+
public function canDecode($value): bool
32+
{
33+
return is_array($value);
34+
}
35+
36+
/** @inheritDoc */
37+
public function canEncode($value): bool
38+
{
39+
return is_array($value);
40+
}
41+
42+
/** @inheritDoc */
43+
public function decode($value): array
44+
{
45+
if (! $this->canDecode($value)) {
46+
throw InvalidArgumentException::invalidType('$value', $value, 'array');
47+
}
48+
49+
return array_map(
50+
/**
51+
* @param mixed $item
52+
* @return mixed
53+
*/
54+
function ($item) {
55+
return $this->getLibrary()->decodeIfSupported($item);
56+
},
57+
$value
58+
);
59+
}
60+
61+
/** @inheritDoc */
62+
public function encode($value): array
63+
{
64+
if (! $this->canEncode($value)) {
65+
throw InvalidArgumentException::invalidType('$value', $value, 'array');
66+
}
67+
68+
return array_map(
69+
/**
70+
* @param mixed $item
71+
* @return mixed
72+
*/
73+
function ($item) {
74+
return $this->getLibrary()->encodeIfSupported($item);
75+
},
76+
$value
77+
);
78+
}
79+
80+
private function getLibrary(): CodecLibrary
81+
{
82+
if (! $this->library) {
83+
$this->library = new CodecLibrary();
84+
}
85+
86+
return $this->library;
87+
}
88+
}

src/Codec/ObjectCodec.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace MongoDB\Codec;
4+
5+
use MongoDB\Exception\InvalidArgumentException;
6+
use stdClass;
7+
8+
use function assert;
9+
use function is_string;
10+
11+
/**
12+
* Codec for lazy decoding of BSON PackedArray instances
13+
*
14+
* @template-implements Codec<stdClass, stdClass>
15+
*/
16+
class ObjectCodec implements Codec, KnowsCodecLibrary
17+
{
18+
/** @template-use DecodeIfSupported<stdClass, stdClass> */
19+
use DecodeIfSupported;
20+
/** @template-use EncodeIfSupported<stdClass, stdClass> */
21+
use EncodeIfSupported;
22+
23+
/** @var CodecLibrary|null */
24+
private $library = null;
25+
26+
public function attachLibrary(CodecLibrary $library): void
27+
{
28+
$this->library = $library;
29+
}
30+
31+
/** @inheritDoc */
32+
public function canDecode($value): bool
33+
{
34+
return $value instanceof stdClass;
35+
}
36+
37+
/** @inheritDoc */
38+
public function canEncode($value): bool
39+
{
40+
return $value instanceof stdClass;
41+
}
42+
43+
/** @inheritDoc */
44+
public function decode($value): stdClass
45+
{
46+
if (! $this->canDecode($value)) {
47+
throw InvalidArgumentException::invalidType('$value', $value, 'stdClass');
48+
}
49+
50+
$return = new stdClass();
51+
52+
/** @var mixed $item */
53+
foreach ($value as $key => $item) {
54+
assert(is_string($key));
55+
$return->{$key} = $this->getLibrary()->decodeIfSupported($item);
56+
}
57+
58+
return $return;
59+
}
60+
61+
/** @inheritDoc */
62+
public function encode($value): stdClass
63+
{
64+
if (! $this->canEncode($value)) {
65+
throw InvalidArgumentException::invalidType('$value', $value, 'stdClass');
66+
}
67+
68+
$return = new stdClass();
69+
70+
/** @var mixed $item */
71+
foreach ($value as $key => $item) {
72+
assert(is_string($key));
73+
$return->{$key} = $this->getLibrary()->encodeIfSupported($item);
74+
}
75+
76+
return $return;
77+
}
78+
79+
private function getLibrary(): CodecLibrary
80+
{
81+
if (! $this->library) {
82+
$this->library = new CodecLibrary();
83+
}
84+
85+
return $this->library;
86+
}
87+
}

src/Codec/architecture.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ Codecs are not inherited from the client or the database object, as they are pur
9797
documents. However, database- or client-level aggregation commands will take an operation-level codec option to
9898
decode the resulting documents.
9999

100+
## Built-in codecs
101+
102+
By default, two codecs are provided: an `ArrayCodec` and an `ObjectCodec`. These two codecs are used to recursively
103+
encode and decode values in arrays and `stdClass` instances, respectively. `ObjectCodec` only handles public properties
104+
in objects and ignores other properties.

tests/Codec/ArrayCodecTest.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\Codec;
4+
5+
use MongoDB\Codec\ArrayCodec;
6+
use MongoDB\Codec\Codec;
7+
use MongoDB\Codec\CodecLibrary;
8+
use MongoDB\Codec\DecodeIfSupported;
9+
use MongoDB\Codec\EncodeIfSupported;
10+
use MongoDB\Tests\TestCase;
11+
12+
class ArrayCodecTest extends TestCase
13+
{
14+
public function testDecodeList(): void
15+
{
16+
$value = [
17+
'magic',
18+
'cigam',
19+
];
20+
21+
$this->assertSame(['magic', 'magic'], $this->getCodec()->decode($value));
22+
}
23+
24+
public function testDecodeListWithGaps(): void
25+
{
26+
$value = [
27+
0 => 'magic',
28+
2 => 'cigam',
29+
];
30+
31+
$this->assertSame([0 => 'magic', 2 => 'magic'], $this->getCodec()->decode($value));
32+
}
33+
34+
public function testDecodeHash(): void
35+
{
36+
$value = [
37+
'foo' => 'magic',
38+
'bar' => 'cigam',
39+
];
40+
41+
$this->assertSame(['foo' => 'magic', 'bar' => 'magic'], $this->getCodec()->decode($value));
42+
}
43+
44+
public function testEncode(): void
45+
{
46+
$value = [
47+
'magic',
48+
'cigam',
49+
];
50+
51+
$this->assertSame(['cigam', 'cigam'], $this->getCodec()->encode($value));
52+
}
53+
54+
public function testEncodeListWithGaps(): void
55+
{
56+
$value = [
57+
0 => 'magic',
58+
2 => 'cigam',
59+
];
60+
61+
$this->assertSame([0 => 'cigam', 2 => 'cigam'], $this->getCodec()->encode($value));
62+
}
63+
64+
public function testEncodeHash(): void
65+
{
66+
$value = [
67+
'foo' => 'magic',
68+
'bar' => 'cigam',
69+
];
70+
71+
$this->assertSame(['foo' => 'cigam', 'bar' => 'cigam'], $this->getCodec()->encode($value));
72+
}
73+
74+
private function getCodec(): ArrayCodec
75+
{
76+
$arrayCodec = new ArrayCodec();
77+
$arrayCodec->attachLibrary($this->getCodecLibrary());
78+
79+
return $arrayCodec;
80+
}
81+
82+
private function getCodecLibrary(): CodecLibrary
83+
{
84+
return new CodecLibrary(
85+
/** @template-implements Codec<string, string> */
86+
new class implements Codec
87+
{
88+
use DecodeIfSupported;
89+
use EncodeIfSupported;
90+
91+
public function canDecode($value): bool
92+
{
93+
return $value === 'cigam';
94+
}
95+
96+
public function canEncode($value): bool
97+
{
98+
return $value === 'magic';
99+
}
100+
101+
public function decode($value)
102+
{
103+
return 'magic';
104+
}
105+
106+
public function encode($value)
107+
{
108+
return 'cigam';
109+
}
110+
}
111+
);
112+
}
113+
}

0 commit comments

Comments
 (0)