Skip to content

Commit 481cffa

Browse files
authored
Pre-init all sub-encoders of the builder encoder (#1613)
Use weak reference to prevent circular reference Avoid having the same encoder initialized multiple times
1 parent 36fca42 commit 481cffa

File tree

3 files changed

+35
-33
lines changed

3 files changed

+35
-33
lines changed

src/Builder/BuilderEncoder.php

+21-29
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,39 @@
2929
use MongoDB\Codec\Encoder;
3030
use MongoDB\Exception\UnsupportedValueException;
3131
use stdClass;
32+
use WeakReference;
3233

3334
use function array_key_exists;
3435
use function is_object;
35-
use function is_string;
3636

3737
/** @template-implements Encoder<Type|stdClass|array|string|int, Pipeline|StageInterface|ExpressionInterface|QueryInterface> */
3838
final class BuilderEncoder implements Encoder
3939
{
4040
/** @template-use EncodeIfSupported<Type|stdClass|array|string|int, Pipeline|StageInterface|ExpressionInterface|QueryInterface> */
4141
use EncodeIfSupported;
4242

43-
/** @var array<class-string, class-string<Encoder>> */
44-
private array $defaultEncoders = [
45-
Pipeline::class => PipelineEncoder::class,
46-
Variable::class => VariableEncoder::class,
47-
DictionaryInterface::class => DictionaryEncoder::class,
48-
FieldPathInterface::class => FieldPathEncoder::class,
49-
CombinedFieldQuery::class => CombinedFieldQueryEncoder::class,
50-
QueryObject::class => QueryEncoder::class,
51-
OutputWindow::class => OutputWindowEncoder::class,
52-
OperatorInterface::class => OperatorEncoder::class,
53-
DateTimeInterface::class => DateTimeEncoder::class,
54-
];
43+
/** @var array<class-string, Encoder> */
44+
private array $encoders;
5545

5646
/** @var array<class-string, Encoder|null> */
5747
private array $cachedEncoders = [];
5848

59-
/** @param array<class-string, Encoder> $customEncoders */
60-
public function __construct(private readonly array $customEncoders = [])
49+
/** @param array<class-string, Encoder> $encoders */
50+
public function __construct(array $encoders = [])
6151
{
52+
$self = WeakReference::create($this);
53+
54+
$this->encoders = $encoders + [
55+
Pipeline::class => new PipelineEncoder($self),
56+
Variable::class => new VariableEncoder(),
57+
DictionaryInterface::class => new DictionaryEncoder(),
58+
FieldPathInterface::class => new FieldPathEncoder(),
59+
CombinedFieldQuery::class => new CombinedFieldQueryEncoder($self),
60+
QueryObject::class => new QueryEncoder($self),
61+
OutputWindow::class => new OutputWindowEncoder($self),
62+
OperatorInterface::class => new OperatorEncoder($self),
63+
DateTimeInterface::class => new DateTimeEncoder(),
64+
];
6265
}
6366

6467
/** @psalm-assert-if-true object $value */
@@ -89,25 +92,14 @@ private function getEncoderFor(object $value): Encoder|null
8992
return $this->cachedEncoders[$valueClass];
9093
}
9194

92-
$encoderList = $this->customEncoders + $this->defaultEncoders;
93-
9495
// First attempt: match class name exactly
95-
if (isset($encoderList[$valueClass])) {
96-
$encoder = $encoderList[$valueClass];
97-
if (is_string($encoder)) {
98-
$encoder = new $encoder($this);
99-
}
100-
101-
return $this->cachedEncoders[$valueClass] = $encoder;
96+
if (isset($this->encoders[$valueClass])) {
97+
return $this->cachedEncoders[$valueClass] = $this->encoders[$valueClass];
10298
}
10399

104100
// Second attempt: catch child classes
105-
foreach ($encoderList as $className => $encoder) {
101+
foreach ($this->encoders as $className => $encoder) {
106102
if ($value instanceof $className) {
107-
if (is_string($encoder)) {
108-
$encoder = new $encoder($this);
109-
}
110-
111103
return $this->cachedEncoders[$valueClass] = $encoder;
112104
}
113105
}

src/Builder/Encoder/QueryEncoder.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function encode(mixed $value): stdClass
5353
throw new LogicException(sprintf('Duplicate key "%s" in query object', $key));
5454
}
5555

56-
$result->{$key} = $this->encoder->encodeIfSupported($value);
56+
$result->{$key} = $this->recursiveEncode($value);
5757
}
5858
}
5959

src/Builder/Encoder/RecursiveEncode.php

+13-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
namespace MongoDB\Builder\Encoder;
66

7-
use MongoDB\Builder\BuilderEncoder;
7+
use MongoDB\Codec\Encoder;
88
use stdClass;
9+
use WeakReference;
910

1011
use function get_object_vars;
1112
use function is_array;
1213

14+
/** @internal */
1315
trait RecursiveEncode
1416
{
15-
final public function __construct(protected readonly BuilderEncoder $encoder)
17+
/** @param WeakReference<Encoder> $encoder */
18+
final public function __construct(private readonly WeakReference $encoder)
1619
{
1720
}
1821

@@ -44,6 +47,13 @@ private function recursiveEncode(mixed $value): mixed
4447
return $value;
4548
}
4649

47-
return $this->encoder->encodeIfSupported($value);
50+
/**
51+
* If the BuilderEncoder instance is removed from the memory, the
52+
* instances of the classes using this trait will be removed as well.
53+
* Therefore, the weak reference will never return null.
54+
*
55+
* @psalm-suppress PossiblyNullReference
56+
*/
57+
return $this->encoder->get()->encodeIfSupported($value);
4858
}
4959
}

0 commit comments

Comments
 (0)