phoenix icon indicating copy to clipboard operation
phoenix copied to clipboard

[FEATURE] EntityListenerInterface for autowiring

Open DonCallisto opened this issue 6 years ago • 21 comments

As symfony >= 3.3 brings autowiring function, I think it could be somehow useful to have an "empty" interface called EntityListenerInterface to take advantage of the autowiring features. This way we can auto-tag all services that implements the interface with doctrine.entity_listener instead of doing it manually.

So, something like

// Doctrine\Bundle\DoctrineBundle\DoctrineBundle.php
/**
 * {@inheritDoc}
 */
public function build(ContainerBuilder $container)
{
    [...]
    $container
        ->registerForAutoconfiguration(EntityListenerInterface::class)
        ->addTag('doctrine.entity_listener');
}

Of course this will not be compatible with all sf supported version so we should think how include it properly (checking sf version, or checking method definition, and so on) or leave it for a release were sf minimum supported version is compliant with this feature.

WDYT?

DonCallisto avatar May 21 '18 08:05 DonCallisto

These empty interfaces isn’t ideal. But, the entity listeners are already kinda magic, where iirc Doctrine looks for specific methods by name. So, a marker interface like this wouldn’t bother me.

However, there is a problem if you want to specify this on a different entity manager other than default. In that case, you would have to tag the service manually. That’s ok, but thanks to autoconfigure, you would now have 2 tags. I think this could be fixed by adding some special attribute to the autoconfgure tag, then removing that tag in the compiler pass, if there are multiple.

If you want to open a PR, you can. I like the idea, but I’m not in charge here :).

weaverryan avatar May 22 '18 00:05 weaverryan

Ok, I'll wait for other to check and validate our idea and then try to write an implementation. Thanks for reviewing this.

DonCallisto avatar May 22 '18 06:05 DonCallisto

We can not use the shared interfaces for autowiring as they conflict with ODM and etc. It is a mistake that we already accept some of them and i want to kill them off as soon as i can.

I agree with @weaverryan that a empty interface is not something we should do. If we had a subscriber interface for ORM only and we could solve the "which entity manager" issue i would be quite open for the feature.

kimhemsoe avatar May 23 '18 09:05 kimhemsoe

and we could solve the "which entity manager"

I think we can solve this

If we had a subscriber interface for ORM only

Yea, that would be best. So, if you really want this feature (it would be cool), we would need to propose that to the ORM first I think.

Btw, there is something easier that can be done now. Doctrine does have normal event subscribers (with an interface). But these do not play well with autoconfiguration yet. That could be done now, entirely in this bundle.

weaverryan avatar May 24 '18 02:05 weaverryan

Doctrine does have normal event subscribers (with an interface). But these do not play well with autoconfiguration yet. That could be done now, entirely in this bundle.

How the subscribers comes in handy here?

Another thought I had is, can't we leverege on annotations/configuration for the entities with EntityListeners? Maybe in a CompilerPass?

DonCallisto avatar May 24 '18 08:05 DonCallisto

Looking at this for a future release. I like the idea of an interface for autoconfiguration, but the entityManager, event and entity attributes of the tag bother me a bit. When tagging manually, you can tag the listener for multiple combinations of managers and entity classes:

<tag name="doctrine.orm.entity_listener" event="preUpdate" entity="User" />
<tag name="doctrine.orm.entity_listener" entity="Group" entity_manager="custom" />

This would register the same service for the preUpdate event for the User entity in the default document manager and the Group entity for all events in the custom entity manager. However, you can't replicate this with methods like getEntityManager, getEvent, and getEntity in the listener interface.

Opinions @DonCallisto @weaverryan?

alcaeus avatar Apr 11 '19 06:04 alcaeus

@alcaeus autoconfiguration doesn't prevent you to explicitly define this kind of attributes so it should simply not be an issue.

DonCallisto avatar Apr 11 '19 16:04 DonCallisto

Having auto-configuration would be great! It's one of the last class that is not auto-configured by Symfony/Doctrine.

fabpot avatar Oct 16 '19 09:10 fabpot

@fabpot I'll add this for DoctrineBundle 1.12 which will come out with Symfony 4.4. If anyone wants to contribute this for Hacktoberfest, please go ahead!

alcaeus avatar Oct 16 '19 13:10 alcaeus

If we decide how to proceed I can try to implement something. From what I got with this thread, the main "logical issues" to be faced are:

  • Using or not an empty interface (only for ORM and not ODM?)
  • The role of Compiler Pass (remove duplicate definitions?)
  • The attributes attached to the tagged services
  • Others ... ?

DonCallisto avatar Oct 16 '19 13:10 DonCallisto

  1. Using an empty interface is not unprecedented, see ServiceEntityRepositoryInterface. The interface will only cover ORM, as MongoDB ODM doesn't know any entity (document) listeners yet. See note 3 though.
  2. The compiler pass already exists: https://github.com/doctrine/DoctrineBundle/blob/1.12.x/DependencyInjection/Compiler/EntityListenerPass.php
  3. For the entity_manager, entity, event, method, and lazy options we need to find a solution. This could either be done by using static methods in the interface, or by having a single method return a EntityListenerConfiguration object that returns the attributes for the listener.

Especially the last one is where I'm not sure what is best - any opinions @doctrine/team-symfony-integration?

alcaeus avatar Oct 16 '19 14:10 alcaeus

What about this?

/**
 * A marker interface for Doctrine entity listeners.
 *
 * Add a "public static getDefaultEntityListenerConfiguration(): array" method to define default
 * attributes for the listener, which should return an array with keys:
 *  - entity: App\Entity\TheEntityClassToListenTo
 *  - ...
 */
interface EntityListenerInterface
{
}

nicolas-grekas avatar Oct 18 '19 06:10 nicolas-grekas

So you would have an empty interface and then check whether a method exists to fetch the configuration? What's the advantage of that compared to separate interfaces?

alcaeus avatar Oct 18 '19 12:10 alcaeus

interface ConfiguredEntityListenerInterface extends EntityListenerInterface
{
    public static getDefaultEntityListenerConfiguration(): array;
}

would work too - not inheriting wouldn't as instanceof checks would then be misleading. maybe we can put getDefaultEntityListenerConfiguration inside EntityListenerInterface if forcing one to create the method and return []; is not considered cumbersome.

nicolas-grekas avatar Oct 18 '19 12:10 nicolas-grekas

maybe we can put getDefaultEntityListenerConfiguration inside EntityListenerInterface if forcing one to create the method and return []; is not considered cumbersome.

I prefer having explicit interfaces and document them properly. We've previously had instances where people need to add empty methods without thinking about them and I'd rather avoid that.

The only change I would make is returning a value object instead of an array (with a simple constructor in the value object) so we're not passing around arrays anymore.

alcaeus avatar Oct 18 '19 12:10 alcaeus

The only change I would make is returning a value object instead of an array (with a simple constructor in the value object) so we're not passing around arrays anymore.

I've objected this on Symfony as this is for DI wiring: it doesn't make much sense to me to create semantic types (a VO) to configure a hash map.

nicolas-grekas avatar Oct 18 '19 13:10 nicolas-grekas

On second thought, you're right: the configuration would simply be the same thing we're getting when getting the corresponding tag from the service. An array will do just fine. Thanks!

alcaeus avatar Oct 18 '19 13:10 alcaeus

So this would be fine https://github.com/doctrine/DoctrineBundle/issues/821#issuecomment-543726388 except from the following consideration

maybe we can put getDefaultEntityListenerConfiguration inside EntityListenerInterface if forcing one to create the method and return []; is not considered cumbersome.

Am I right?

DonCallisto avatar Oct 21 '19 07:10 DonCallisto

I like the last suggestion here:

interface ConfiguredEntityListenerInterface extends EntityListenerInterface
{
    public static getDefaultEntityListenerConfiguration(): array;
}

alcaeus avatar Oct 21 '19 07:10 alcaeus

Bear in mind this needs to be able to handle multiple entities in separate methods of same class. Also not sure name is still correct, looks more like subscriber to me.

ostrolucky avatar Dec 01 '19 17:12 ostrolucky

This auto-configuration would be great ! Is this still on the road map ?

VincentLanglet avatar Apr 22 '20 16:04 VincentLanglet

Is this still relevant now that we have AsDoctrineListener (upcoming with 2.8.0) and AsEntityListener attributes?

dmaicher avatar Dec 08 '22 18:12 dmaicher

No. The attribute is a better implementation than an empty marker interface

stof avatar Dec 08 '22 18:12 stof