Skip to content

Commit 47484aa

Browse files
committed
PHPLIB-1635 BuilderEncoder accepts an instance of encoder instead of a class string
1 parent d1aea99 commit 47484aa

File tree

3 files changed

+66
-9
lines changed

3 files changed

+66
-9
lines changed

psalm-baseline.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
<code><![CDATA[stdClass]]></code>
4242
</MoreSpecificReturnType>
4343
</file>
44+
<file src="src/Builder/BuilderEncoder.php">
45+
<MixedReturnStatement>
46+
<code><![CDATA[$encoder->encode($value)]]></code>
47+
</MixedReturnStatement>
48+
</file>
4449
<file src="src/Builder/Encoder/AbstractExpressionEncoder.php">
4550
<MixedAssignment>
4651
<code><![CDATA[$val]]></code>

src/Builder/BuilderEncoder.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ final class BuilderEncoder implements Encoder
4949
OperatorInterface::class => OperatorEncoder::class,
5050
];
5151

52-
/** @var array<class-string, ExpressionEncoder|null> */
52+
/** @var array<class-string, Encoder|null> */
5353
private array $cachedEncoders = [];
5454

55-
/** @param array<class-string, class-string<ExpressionEncoder>> $customEncoders */
55+
/** @param array<class-string, Encoder> $customEncoders */
5656
public function __construct(private readonly array $customEncoders = [])
5757
{
5858
}
@@ -67,7 +67,7 @@ public function canEncode(mixed $value): bool
6767
return (bool) $this->getEncoderFor($value)?->canEncode($value);
6868
}
6969

70-
public function encode(mixed $value): stdClass|array|string|int
70+
public function encode(mixed $value): mixed
7171
{
7272
$encoder = $this->getEncoderFor($value);
7373

@@ -78,7 +78,7 @@ public function encode(mixed $value): stdClass|array|string|int
7878
return $encoder->encode($value);
7979
}
8080

81-
private function getEncoderFor(object $value): ExpressionEncoder|null
81+
private function getEncoderFor(object $value): Encoder|null
8282
{
8383
$valueClass = $value::class;
8484
if (array_key_exists($valueClass, $this->cachedEncoders)) {
@@ -89,13 +89,22 @@ private function getEncoderFor(object $value): ExpressionEncoder|null
8989

9090
// First attempt: match class name exactly
9191
if (isset($encoderList[$valueClass])) {
92-
return $this->cachedEncoders[$valueClass] = new $encoderList[$valueClass]($this);
92+
$encoder = $encoderList[$valueClass];
93+
if (is_string($encoder)) {
94+
$encoder = new $encoder($this);
95+
}
96+
97+
return $this->cachedEncoders[$valueClass] = $encoder;
9398
}
9499

95100
// Second attempt: catch child classes
96-
foreach ($encoderList as $className => $encoderClass) {
101+
foreach ($encoderList as $className => $encoder) {
97102
if ($value instanceof $className) {
98-
return $this->cachedEncoders[$valueClass] = new $encoderClass($this);
103+
if (is_string($encoder)) {
104+
$encoder = new $encoder($this);
105+
}
106+
107+
return $this->cachedEncoders[$valueClass] = $encoder;
99108
}
100109
}
101110

tests/Builder/BuilderEncoderTest.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
use MongoDB\Builder\Pipeline;
1313
use MongoDB\Builder\Query;
1414
use MongoDB\Builder\Stage;
15+
use MongoDB\Builder\Type\FieldPathInterface;
1516
use MongoDB\Builder\Type\Sort;
1617
use MongoDB\Builder\Variable;
18+
use MongoDB\Codec\EncodeIfSupported;
19+
use MongoDB\Codec\Encoder;
1720
use PHPUnit\Framework\Attributes\DataProvider;
1821
use PHPUnit\Framework\TestCase;
1922

@@ -337,10 +340,50 @@ public function testRedactStage(): void
337340
$this->assertSamePipeline($expected, $pipeline);
338341
}
339342

343+
public function testCustomEncoder(): void
344+
{
345+
$customEncoders = [
346+
FieldPathInterface::class => new class implements Encoder {
347+
use EncodeIfSupported;
348+
349+
public function canEncode(mixed $value): bool
350+
{
351+
return $value instanceof FieldPathInterface;
352+
}
353+
354+
public function encode(mixed $value)
355+
{
356+
return '$prefix.' . $value->name;
357+
}
358+
},
359+
];
360+
$codec = new BuilderEncoder($customEncoders);
361+
362+
$pipeline = new Pipeline(
363+
Stage::project(
364+
threeFavorites: Expression::slice(
365+
Expression::arrayFieldPath('items'),
366+
n: 3,
367+
),
368+
),
369+
);
370+
371+
$expected = [
372+
[
373+
'$project' => [
374+
'threeFavorites' => [
375+
'$slice' => ['$prefix.items', 3],
376+
],
377+
],
378+
],
379+
];
380+
381+
$this->assertSamePipeline($expected, $pipeline, $codec);
382+
}
383+
340384
/** @param list<array<string, mixed>> $expected */
341-
private static function assertSamePipeline(array $expected, Pipeline $pipeline): void
385+
private static function assertSamePipeline(array $expected, Pipeline $pipeline, BuilderEncoder $codec = new BuilderEncoder()): void
342386
{
343-
$codec = new BuilderEncoder();
344387
$actual = $codec->encode($pipeline);
345388

346389
// Normalize with BSON round-trip

0 commit comments

Comments
 (0)