Skip to content

Commit

Permalink
[DoctrineBridge] Add argument to EntityValueResolver to set type aliases
Browse files Browse the repository at this point in the history
This allows for fixing #51765; with a consequential Doctrine bundle update, the resolve_target_entities configuration can be injected similarly to ResolveTargetEntityListener in the Doctrine codebase.

Alternatively the config and ValueResolver can be injected using a compiler pass in the Symfony core code, however the value resolver seems to be configured in the Doctrine bundle already.
  • Loading branch information
NanoSector committed Apr 11, 2024
1 parent 9b323c6 commit a04cfae
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,27 @@
*/
final class EntityValueResolver implements ValueResolverInterface
{
/** @var array<class-string, class-string> */
private array $typeAliases = [];

public function __construct(
private ManagerRegistry $registry,
private ?ExpressionLanguage $expressionLanguage = null,
private MapEntity $defaults = new MapEntity(),
) {
}

/**
* Adds an original class name to resolve to a target class name.
*
* @param class-string $original
* @param class-string $target
*/
public function addTypeAlias(string $original, string $target): void
{
$this->typeAliases[ltrim($original, '\\')] = ltrim($target, '\\');
}

public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (\is_object($request->attributes->get($argument->getName()))) {
Expand All @@ -50,6 +64,13 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
if (!$options->class || $options->disabled) {
return [];
}

if (isset($this->typeAliases[$options->class])) {
// Overwriting the property of MapEntity at this point is safe,
// since withDefaults called above created a clone of the original object.
$options->class = $this->typeAliases[$options->class];
}

if (!$manager = $this->getManager($options->objectManager, $options->class)) {
return [];
}
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Doctrine/Attribute/MapEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function __construct(
public function withDefaults(self $defaults, ?string $class): static
{
$clone = clone $this;
$clone->class ??= class_exists($class ?? '') ? $class : null;
$clone->class ??= class_exists($class ?? '') || interface_exists($class ?? '', false) ? $class : null;
$clone->objectManager ??= $defaults->objectManager;
$clone->expr ??= $defaults->expr;
$clone->mapping ??= $defaults->mapping;
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bridge/Doctrine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
7.1
---

* Add type aliases support to `EntityValueResolver` through the `addTypeAlias` method
* Deprecate the `DoctrineExtractor::getTypes()` method, use `DoctrineExtractor::getType()` instead

7.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,42 @@ public function testResolveWithId(string|int $id)
$this->assertSame([$object], $resolver->resolve($request, $argument));
}

/**
* @dataProvider idsProvider
*/
public function testResolveWithIdAndTypeAlias(string|int $id)
{
$manager = $this->getMockBuilder(ObjectManager::class)->getMock();
$registry = $this->createRegistry($manager);
$resolver = new EntityValueResolver(
$registry,
null,
new MapEntity(),
);
// Using \Throwable here because it is built in to PHP and an interface.
$resolver->addTypeAlias('Throwable', 'stdClass');

$request = new Request();
$request->attributes->set('id', $id);

$argument = $this->createArgument('Throwable', $mapEntity = new MapEntity(id: 'id'));

$repository = $this->getMockBuilder(ObjectRepository::class)->getMock();
$repository->expects($this->once())
->method('find')
->with($id)
->willReturn($object = new \stdClass());

$manager->expects($this->once())
->method('getRepository')
->with('stdClass')
->willReturn($repository);

$this->assertSame([$object], $resolver->resolve($request, $argument));
// Ensure the original MapEntity object was not updated
$this->assertNull($mapEntity->class);
}

public function testResolveWithNullId()
{
$manager = $this->getMockBuilder(ObjectManager::class)->getMock();
Expand Down

0 comments on commit a04cfae

Please sign in to comment.