Skip to content

Commit

Permalink
Pre-init all sub-encoders of the builder encoder (#1613)
Browse files Browse the repository at this point in the history
Use weak reference to prevent circular reference
Avoid having the same encoder initialized multiple times
  • Loading branch information
GromNaN authored Feb 26, 2025
1 parent 36fca42 commit 481cffa
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 33 deletions.
50 changes: 21 additions & 29 deletions src/Builder/BuilderEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,39 @@
use MongoDB\Codec\Encoder;
use MongoDB\Exception\UnsupportedValueException;
use stdClass;
use WeakReference;

use function array_key_exists;
use function is_object;
use function is_string;

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

/** @var array<class-string, class-string<Encoder>> */
private array $defaultEncoders = [
Pipeline::class => PipelineEncoder::class,
Variable::class => VariableEncoder::class,
DictionaryInterface::class => DictionaryEncoder::class,
FieldPathInterface::class => FieldPathEncoder::class,
CombinedFieldQuery::class => CombinedFieldQueryEncoder::class,
QueryObject::class => QueryEncoder::class,
OutputWindow::class => OutputWindowEncoder::class,
OperatorInterface::class => OperatorEncoder::class,
DateTimeInterface::class => DateTimeEncoder::class,
];
/** @var array<class-string, Encoder> */
private array $encoders;

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

/** @param array<class-string, Encoder> $customEncoders */
public function __construct(private readonly array $customEncoders = [])
/** @param array<class-string, Encoder> $encoders */
public function __construct(array $encoders = [])
{
$self = WeakReference::create($this);

$this->encoders = $encoders + [
Pipeline::class => new PipelineEncoder($self),
Variable::class => new VariableEncoder(),
DictionaryInterface::class => new DictionaryEncoder(),
FieldPathInterface::class => new FieldPathEncoder(),
CombinedFieldQuery::class => new CombinedFieldQueryEncoder($self),
QueryObject::class => new QueryEncoder($self),
OutputWindow::class => new OutputWindowEncoder($self),
OperatorInterface::class => new OperatorEncoder($self),
DateTimeInterface::class => new DateTimeEncoder(),
];
}

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

$encoderList = $this->customEncoders + $this->defaultEncoders;

// First attempt: match class name exactly
if (isset($encoderList[$valueClass])) {
$encoder = $encoderList[$valueClass];
if (is_string($encoder)) {
$encoder = new $encoder($this);
}

return $this->cachedEncoders[$valueClass] = $encoder;
if (isset($this->encoders[$valueClass])) {
return $this->cachedEncoders[$valueClass] = $this->encoders[$valueClass];
}

// Second attempt: catch child classes
foreach ($encoderList as $className => $encoder) {
foreach ($this->encoders as $className => $encoder) {
if ($value instanceof $className) {
if (is_string($encoder)) {
$encoder = new $encoder($this);
}

return $this->cachedEncoders[$valueClass] = $encoder;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Builder/Encoder/QueryEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function encode(mixed $value): stdClass
throw new LogicException(sprintf('Duplicate key "%s" in query object', $key));
}

$result->{$key} = $this->encoder->encodeIfSupported($value);
$result->{$key} = $this->recursiveEncode($value);
}
}

Expand Down
16 changes: 13 additions & 3 deletions src/Builder/Encoder/RecursiveEncode.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

namespace MongoDB\Builder\Encoder;

use MongoDB\Builder\BuilderEncoder;
use MongoDB\Codec\Encoder;
use stdClass;
use WeakReference;

use function get_object_vars;
use function is_array;

/** @internal */
trait RecursiveEncode
{
final public function __construct(protected readonly BuilderEncoder $encoder)
/** @param WeakReference<Encoder> $encoder */
final public function __construct(private readonly WeakReference $encoder)
{
}

Expand Down Expand Up @@ -44,6 +47,13 @@ private function recursiveEncode(mixed $value): mixed
return $value;
}

return $this->encoder->encodeIfSupported($value);
/**
* If the BuilderEncoder instance is removed from the memory, the
* instances of the classes using this trait will be removed as well.
* Therefore, the weak reference will never return null.
*
* @psalm-suppress PossiblyNullReference
*/
return $this->encoder->get()->encodeIfSupported($value);
}
}

0 comments on commit 481cffa

Please sign in to comment.