laminas / laminas-eventmanager

Trigger and listen to events within a PHP application
https://docs.laminas.dev/laminas-eventmanager/
BSD 3-Clause "New" or "Revised" License
1.01k stars 13 forks source link

Question - Best way for modules to attach listeners #7

Open weierophinney opened 4 years ago

weierophinney commented 4 years ago

Hello First my apologies if this is not the right place to ask questions. Please let me know if there is a better place - Thank you


Here is my issue
I was wondering what would be the best way to attach listeners to an Event Manager from a Different Module

Here is my scenario, I have a user form lets call it Application\UserForm

In its class factory I inject an Event Manager and a Shared Event Manager

factory:

$userForm = new UserForm();

$sharedEventManager = $container->get('FormSharedEventManager');
$eventManager =  new EventManager($sharedEventManager);

$userForm->setEventManager($eventManager);

What is the Best way for another module (lets call it Module X) to attach its listener to this Form (and any other forms really that use the same FormSharedEventManager ) ?

Here are my choices

Delegators

I could add Delegators in the Module X Config

Option 1: at the UserForm level --> so that everytime UserForm is created My delegator class would fetch the event manager and directly attach listeners

Option 2 -> Same idea but at the shared event manager level Everytime FormSharedEventManager is created i would attach the listeners (That also allow me to use the identifiers )

Config file parameters

Option 3: i could create a new config parameter

return [
  'shared_event_manager_listeners' => [ 
        'FormSharedEventManager::class => [
                 ModuleXFormListenersAggregate::class
         ]
  ]
];

And I would have a to use a new Factory to create FormSharedEventManager

'factories' => [
    FormSharedEventManager::class => AggregateAwareSharedEventManagerFACTORY::class
]

My new Factory (AggregateAwareSharedEventManagerFACTORY) Would then fetch all the listeners aggregates from the "shared_event_manager_listers" config attached to the requested class (FormSharedEventManager::class)

I want to use the fastest or cleanest way to attach those listeners Ideally the Module would also add listeners to other part of the process

Pros and Cons

Option 1:

Option 2:

Option 3:


Ideally Module X would also want to listen to other events like Controller Events For e.g When a user submit the form Module X will want to send an email Would it be smarter to Inject the default Shared Event Manager into my Form instead of creating a new FormSharedEventManager and attaching the listeners into the Default shared event manager ?


Originally posted by @cgaube at https://github.com/zendframework/zend-eventmanager/issues/40

weierophinney commented 4 years ago

Good question. I resolve it by creating module for that: https://github.com/snapshotpl/ZfConfigListener


Originally posted by @snapshotpl at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256484494

weierophinney commented 4 years ago

So your module is kind-of a mix and matches of the options I described You are using a new configuration in the module.config files that defines what listeners should be attached to what service
Then your module, during the "merge config" event, will attach a delegator to each of those services The delegator then just fetch the listeners and attach them to the service's event manager

This is nice but you are limiting yourself to services defined in the service_manager That would not work for my scenario because my form would be defined in the Form Element Manager (form_elements)

Thank you for your input though I am going to use it to build the perfect solution for me


Originally posted by @cgaube at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256520938

weierophinney commented 4 years ago

There is a big problem with delegators with zend-eventmanager >= 3.0. Please see my issue zendframework/zend-mvc#205.

The event manager in classes that implements EventManagerAwareInterface will be overrode by an initializer in zend-mvc because it checks if getSharedManager() is an instance of SharedEventManagerInterface.

The EventManagerAwareTrait in zend-eventmanager >= 3.0 does not create the shared manager anymore, so the whole event manager will be overrode in the initializer, and after delegators, losing the attached listeners.

If you want to use delegators, you should be sure that a complete event manager with the shared event manager is injected in the object.


Originally posted by @thomasvargiu at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256583990

weierophinney commented 4 years ago

If you want to use delegators, you should be sure that a complete event manager with the shared event manager is injected in the object.

This is what I was planning to do anyway, because like you said, the SharedEvent Manager is not injected to any event manager by default anymore -

See my first comment

$userForm = new UserForm();

$sharedEventManager = $container->get('FormSharedEventManager');
$eventManager =  new EventManager($sharedEventManager);

$userForm->setEventManager($eventManager);

Doing so allow you to have full control on the event manager I like also like to create an Event Class eventPrototype for each Event Managers - For e.g My Event Manager injected in my UserForm use a new Event Type called \FormEvent and not \Zend\EventManager\Event

$eventManager->setEventPrototype( new FormEvent() );

Originally posted by @cgaube at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256676735

weierophinney commented 4 years ago

Why would you use another SharedEventManager instance for your service?

You can do something like this with zend-mvc:

$userForm = new UserForm();

// zend-mvc defines the 'EventManager' not shared, creating a new EventManager each time
$eventManager =  $container->get('EventManager');

$userForm->setEventManager($eventManager);

For libraries without zend-mvc I would suggest something like this:

$userForm = new UserForm();

// zend-mvc defines the 'SharedEventManager'
$sharedEventManager = $container->has('SharedEventManager') ? $container->get('SharedEventManager') : null;
$eventManager =  new EventManager($sharedEventManager);

$userForm->setEventManager($eventManager);

In this way you can use the default SharedEventManager, using the identifiers.


Originally posted by @thomasvargiu at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256684143

weierophinney commented 4 years ago

Probably because I did not know/check that zend-mvc was defining a service called "EventManager" that was automatically inject the default SharedEventManager.

This is why I opened that discussion - I am trying to understand what would be the best way to do it Thank you for your input

Why would you use another SharedEventManager instance for your service? To separate Functionalities by Class Type - I was thinking that It might be faster and cleaner to have several shared event managers So I could have a FormSharedManager that would only apply to Form Classes (like UserForm) I could still use the identifiers to filter by class name (UserForm / PermissionForm ... ),

I could also have another separate ShareEventManager for my Models etc etc

Is this method overkill ? Should I just attach all my listeners to the default SharedEvent manager ?


Originally posted by @cgaube at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256692367

weierophinney commented 4 years ago

It's just my opinion. You can inject another SharedEvent manager, but I suggest to use a "standard", like the "SharedEventManager" service of zend-mvc. In this way, anyone can attach listeners without to know which SharedManager is.


Originally posted by @thomasvargiu at https://github.com/zendframework/zend-eventmanager/issues/40#issuecomment-256695136