EntityListenerPass unaware of ResolveTargetEntityListener::$resolveTargetEntities #7178

Open
opened 2026-01-22 15:46:07 +01:00 by admin · 1 comment
Owner

Originally created by @BreyndotEchse on GitHub (Jul 10, 2023).

Feature Request

Q A
New Feature yes
RFC yes
BC Break no

Summary

The Doctrine\ORM\Tools\AttachEntityListenersListener does not attach any entity listeners targeting interfaces or abstract classes, because it is not aware of the Doctrine\ORM\Tools\ResolveTargetEntityListener::$resolveTargetEntities used to resolve and load ClassMetadata of these interfaces or classes.

Dysfunctional config example:

# doctrine.yaml
doctrine:
    orm:
        resolve_target_entities:
            FooInterface: Foo
# services.yaml
services:
    FooListener:
        tags:
            - { name: doctrine.orm.entity_listener, entity: FooInterface }
#                                                              ^- Works only without "Interface" suffix

So what I would like to do is:

  • Move ResolveTargetEntityListener::$resolveTargetEntities to separate service: TargetEntityResolver
  • Inject TargetEntityResolver service into ResolveTargetEntityListener and AttachEntityListenersListener
  • Forward ResolveTargetEntityListener::addResolveTargetEntity() calls to TargetEntityResolver::addResolveTargetEntity()
  • Add @deprecated doc to ResolveTargetEntityListener::addResolveTargetEntity()
  • Use resolved entities to attach listeners in AttachEntityListenersListener

Any concerns?

(Copied from https://github.com/doctrine/DoctrineBundle/issues/1683)

Originally created by @BreyndotEchse on GitHub (Jul 10, 2023). ### Feature Request | Q | A |------------ | ------ | New Feature | yes | RFC | yes | BC Break | no #### Summary The `Doctrine\ORM\Tools\AttachEntityListenersListener` does not attach any entity listeners targeting interfaces or abstract classes, because it is not aware of the `Doctrine\ORM\Tools\ResolveTargetEntityListener::$resolveTargetEntities` used to resolve and load ClassMetadata of these interfaces or classes. Dysfunctional config example: ```yaml # doctrine.yaml doctrine: orm: resolve_target_entities: FooInterface: Foo ``` ```yaml # services.yaml services: FooListener: tags: - { name: doctrine.orm.entity_listener, entity: FooInterface } # ^- Works only without "Interface" suffix ``` So what I would like to do is: - Move `ResolveTargetEntityListener::$resolveTargetEntities` to separate service: TargetEntityResolver - Inject TargetEntityResolver service into ResolveTargetEntityListener and AttachEntityListenersListener - Forward `ResolveTargetEntityListener::addResolveTargetEntity()` calls to `TargetEntityResolver::addResolveTargetEntity()` - Add `@deprecated` doc to `ResolveTargetEntityListener::addResolveTargetEntity()` - Use resolved entities to attach listeners in AttachEntityListenersListener Any concerns? (Copied from https://github.com/doctrine/DoctrineBundle/issues/1683)
Author
Owner

@BreyndotEchse commented on GitHub (Jul 10, 2023):

@stof https://github.com/doctrine/DoctrineBundle/issues/1683#issuecomment-1625735263
Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata emits the "loadClassMetadata" event. AFAICS this method won't be called if metadata of this class is already cached in Doctrine\Persistence\Mapping\AbstractClassMetadataFactory::$loadedMetadata. So the performance impact of this change would be (almost) zero because Doctrine\ORM\Tools\AttachEntityListenersListener is only called once per entity class at most.

Let's say we have an entity, that depends on external services to serialize/unserialize specific data. Lifecycle callbacks don't support additional arguments, so you have to rely on EntityListeners. Lets's say we have something like this:

interface MyEntityInterface
{
    public function doSomethingToHaveAValidEntityAfterDeserializationFromDBOnLoad($dep1, $dep2): void;
    public function prepareSomethingPreSerializationToDBPreFlush($dep1, $dep2): void;
}

final class MyEntityListener
{
    public function __construct(
        private readonly Dep $dep1,
        private readonly Dep $dep2,
    ) {}

    public function onLoad(MyEntityInterface $entity, OnLoadEventArgs $args): void
    {
        $entity->doSomethingToHaveAValidEntityAfterDeserializationFromDBOnLoad($this->dep1, $this->dep2);
    }

    public function preFlush(MyEntityInterface $entity, PreFlushEventArgs $args): void
    {
        $entity->prepareSomethingPreSerializationToDBPreFlush($this->dep1, $this->dep2);
    }
}

When I then create an entity in another bundle that implements the "MyEntityInterface" interface, would it be on the bundle's part to attache the entity listener? Does not feel right because the interface gives the impression it was already attached by the bundle that it is part of.

@BreyndotEchse commented on GitHub (Jul 10, 2023): @stof https://github.com/doctrine/DoctrineBundle/issues/1683#issuecomment-1625735263 `Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata` emits the "loadClassMetadata" event. AFAICS this method won't be called if metadata of this class is already cached in `Doctrine\Persistence\Mapping\AbstractClassMetadataFactory::$loadedMetadata`. So the performance impact of this change would be (almost) zero because `Doctrine\ORM\Tools\AttachEntityListenersListener` is only called once per entity class at most. Let's say we have an entity, that depends on external services to serialize/unserialize specific data. Lifecycle callbacks don't support additional arguments, so you have to rely on EntityListeners. Lets's say we have something like this: ```php interface MyEntityInterface { public function doSomethingToHaveAValidEntityAfterDeserializationFromDBOnLoad($dep1, $dep2): void; public function prepareSomethingPreSerializationToDBPreFlush($dep1, $dep2): void; } final class MyEntityListener { public function __construct( private readonly Dep $dep1, private readonly Dep $dep2, ) {} public function onLoad(MyEntityInterface $entity, OnLoadEventArgs $args): void { $entity->doSomethingToHaveAValidEntityAfterDeserializationFromDBOnLoad($this->dep1, $this->dep2); } public function preFlush(MyEntityInterface $entity, PreFlushEventArgs $args): void { $entity->prepareSomethingPreSerializationToDBPreFlush($this->dep1, $this->dep2); } } ``` When I then create an entity in another bundle that implements the "MyEntityInterface" interface, would it be on the bundle's part to attache the entity listener? Does not feel right because the interface gives the impression it was already attached by the bundle that it is part of.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7178