-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DoctrineBridge] #[MapEntity] does not resolve an entity from its interface, aliased using resolve_target_entities #51765
Comments
+1 For informations, the issue is also reported in the doctrine/persistence repository (doctrine/persistence#63). |
We've worked around this issue by decorating the EntityValueResolver class and dynamically updating the This consists of two parts; the wrapping ValueResolver, and a container compiler pass to insert the Doctrine configuration into the container: ValueResolver decoratorSymfony should automatically pick this up and configure the service appropriately if it's placed in your project. <?php
declare(strict_types=1);
namespace App\Common\Infrastructure\ValueResolver;
use InvalidArgumentException;
use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* @internal Works around https://github.com/symfony/symfony/issues/51765
*/
#[AsDecorator('doctrine.orm.entity_value_resolver')]
#[AutoconfigureTag('controller.argument_value_resolver')]
final readonly class AliasingEntityValueResolver implements ValueResolverInterface
{
public function __construct(
#[AutowireDecorated]
private EntityValueResolver $inner,
/** @var array<class-string, class-string> */
#[Autowire(param: 'doctrine.orm.resolve_target_entities')]
private array $doctrineTargetEntityAliases,
) {
}
/**
* @return iterable<object>
* @throws NotFoundHttpException
* @throws InvalidArgumentException
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
if (\is_object($request->attributes->get($argument->getName()))) {
return [];
}
$entityMappings = $argument->getAttributes(MapEntity::class, ArgumentMetadata::IS_INSTANCEOF);
/** @var MapEntity|false $entityMapping */
$entityMapping = reset($entityMappings);
if ($entityMapping === false) {
return [];
}
$targetClass = $entityMapping->class
?? $argument->getType()
?? throw new InvalidArgumentException('MapEntity parameters should specify either a type or a class argument');
if (\array_key_exists($targetClass, $this->doctrineTargetEntityAliases)) {
$entityMapping->class = $this->doctrineTargetEntityAliases[$targetClass];
}
return $this->inner->resolve($request, $argument);
}
} Compiler passThis does assume the configuration property is always present. <?php
declare(strict_types=1);
namespace App\Common\Infrastructure\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use RuntimeException;
class DoctrineResolveTargetEntitiesInjectorCompilerPass implements CompilerPassInterface
{
/**
* @throws RuntimeException
*/
public function process(ContainerBuilder $container): void
{
$config = $container->getExtensionConfig('doctrine');
if (
!isset($config[0]['orm']['resolve_target_entities'])
|| !is_array($mapping = $config[0]['orm']['resolve_target_entities'])
) {
throw new RuntimeException(
'doctrine.orm.resolve_target_entities is missing from the bundle configuration',
);
}
$container->setParameter('doctrine.orm.resolve_target_entities', $mapping);
}
} Register it in your Kernel class: <?php
declare(strict_types=1);
namespace App;
use App\Common\Infrastructure\DependencyInjection\DoctrineResolveTargetEntitiesInjectorCompilerPass;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
final class Kernel extends BaseKernel
{
use MicroKernelTrait;
// ...
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new DoctrineResolveTargetEntitiesInjectorCompilerPass());
}
// ...
} |
This allows for fixing symfony#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.
This allows for fixing symfony#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.
This allows for fixing symfony#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.
This allows for fixing symfony#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.
This is an addendum to PR symfony/symfony#51765 in the Symfony Doctrine Bridge, which adds type alias support to EntityValueResolver. This code injects the doctrine.orm.resolve_target_entities configuration into the EntityValueResolver class.
This is an addendum to PR symfony/symfony#51765 in the Symfony Doctrine Bridge, which adds type alias support to EntityValueResolver. This code injects the doctrine.orm.resolve_target_entities configuration into the EntityValueResolver class.
This allows for fixing symfony#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.
This is an addendum to PR symfony/symfony#51765 in the Symfony Doctrine Bridge, which adds type alias support to EntityValueResolver. This code injects the doctrine.orm.resolve_target_entities configuration into the EntityValueResolver class.
This allows for fixing symfony#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.
Symfony version(s) affected
6.3.x
Description
#[MapEntity]
attribute does not resolve an entity from its interface names, even it is implemented on the entity and aliased usingdoctrine.orm.resolve_target_entities
configuration.Error screen says:
How to reproduce
config/packages/doctrine.yaml
src/Entity/UserInterface.php
src/Entity/User.php
src/Controller/Controller.php
Possible Solution
Problem 1:
#[MapEntity]
does not accept interfacesMapEntity checks the class existence by
class_exists
. For interface names, the function will return false even if it exists. We can useclass_exists($class) || interface_exists($class)
for resolve this.Problem 2: EntityValueResolver uses ClassMetadataFactory::isTransient and it does not load metadata
To resolve entities aliased in
resolve_target_entities
, we have to load their metadata before using.ClassMetadataFactory::isTransient
does not do so, then it will returntrue
. We should explicitly load metadata to resolve entities.Additional Context
Specifying the actual entity as
#[MapEntity(class: User::class)]
do a trick, but we actually separate the interface and the entity in different repository (integrated using Symfony Bundle system).The text was updated successfully, but these errors were encountered: