doctrine / DoctrineBundle

Symfony Bundle for Doctrine ORM and DBAL

Home Page:https://www.doctrine-project.org/projects/doctrine-bundle.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

EntityListener can't be applied to multiple services with same class name

biozshock opened this issue · comments

With current implementation of ContainerEntityListenerResolver it's not possible to use same class, even if it has different service IDs

Consider following config:

services:
  service_a:
    class: My\Class
    parameters: ['param1`]
    tags:
      - {'name': 'doctrine.orm.entity_listener', 'entity': 'My\Entity1'}

  service_b:
    class: My\Class
    parameters: ['param2`]
    tags:
      - {'name': 'doctrine.orm.entity_listener', 'entity': 'My\Entity2'}

The entity listener service_b will be applied to entity My\Entity1, because https://github.com/doctrine/DoctrineBundle/blob/2.1.2/Mapping/ContainerEntityListenerResolver.php#L66 uses class name instead of the service ID to collect entity listeners.

AFAICT, this is because the way the ORM works is that it uses a class name as the input of the resolver (and so does not allow having multiple ones with the same class name)

This is because how a class metadata is collected in EntityListenerPass. Doctrine does not operate with class names here, but with strings. I'm positive that DoctrineBundle can put service IDs instead of real class names, and it will just work.
Though i'm not really sure how to implement such logic for services that are marked as lazy. Because an interface accepts only an instance of the listener, w/o the ID.

The interface in the ORM explicitly declares the argument as being a class name rather than an arbitrary string: https://github.com/doctrine/orm/blob/e0eb82a3b12a3fd0878b5d56c3c293c04c168329/lib/Doctrine/ORM/Mapping/EntityListenerResolver.php#L42-L51

In terms of the symfony bundle that's an ID of the service.

Not in your example. If you want to discrespect the contract, you can do it on your own, but we don't want to do that in a bundle. Another option is to open issue in ORM which relaxes the contract.

@biozshock no. The ContainerEntityListenerResolver lookups the provided argument into the ServiceLocator it takes as argument. So it can indeed work with any kind of string internally as long as both sides are matching. But the expectation of the signature are defined by the interface it implements, which is why the ServiceLocator is configured with class names as keys.

Marking on hold until contract is changed in ORM

Change unblocking this has been merged upstream, so this is ready to implement now

Hmm I think we still can't do anything here, because resolve method must return 1 object (listener) only. Unfortunately you will have to ask in https://github.com/doctrine/orm to change its event system