|
By default the MVC Application Event Manager is not a shared service
object and it has its own Shared Event Manager. However this particular Shared Event Manager is not shared with other Event Managers and by default all the other Event Managers will fall back to using the Static Event Manager as their Shared Event Manager. One scenario is that you have two independently configured Service Managers, if the same component (not instance) ends up utilizing the (static) shared event manager, then, to me, suggests that developer has to figure out and ensure that the listeners invoked via one service object will not affect/invoke the listeners via the other Service Manager.... Isn't this way to confusing? Another scenario is that within the bootstrap of a module, then it is not possible to attach a listener to another Event Manager whose identifier is not 'application'. e.g $em = $event->getApplication()->events()->getSharedManager(); $em->attach('member', 'addMember', function() { var_dump(__LINE__.' '.__FILE__); }); The above means that the Member Service Component would have its own Event Manager and by default using the Static Event Manager as the shared event manager. But the above will not work, because the MVC Application Event Manager does not use the same shared event manager as the other event managers. Which means having to changing the identifier to 'application' (or injecting the MVC Application Shared Event Manager into all the other Event Managers, etc). $em = $event->getApplication()->events()->getSharedManager(); $em->attach('application', 'addMember', function() { var_dump(__LINE__.' '.__FILE__); }); However the latter above means that the Member Service Component must then use the MVC Application Event Manager. But setting $shared = array('EventManager' => true) bombs out within a nested loop (related to ControllerLoader and the index Controller). I think the Shared Event Manager should be just that, one by default that is shared by all Event Managers and is not static but always injected. This I think suggests getting it from the Service Manager and automatically injecting via say a SharedEventManagerAwareInterface. I think that it should be highly recommended not to use class names etc as identifiers, but use aliases instead e.g. 'application'. If the Shared Event Managers do end up playing more nicely together (e.g the MVC Application Shared Event Manager is the same one that the other Event Managers use), then it might be worth considering removing the 'identifier' completely and just have unique event names (this could assume that an Event Manager should be aware of all the events that it supports?). Another reason supporting this is, if the MVC Application Event Manager is shared by all, then the identifier param in its Shared Event Manager becomes redundant? |
|
Administrator
|
-- Greg <[hidden email]> wrote
(on Saturday, 30 June 2012, 01:30 PM -0500): > By default the MVC Application Event Manager is not a shared service > object and it has its own Shared Event Manager. However this > particular Shared Event Manager is not shared with other Event > Managers and by default all the other Event Managers will fall back to > using the Static Event Manager as their Shared Event Manager. Wrong. If you use the ServiceManager, and use the EventManager factory, a single instance of SharedEventManager is injected into each EventManager instance. The fallback to the StaticEventManager should only happen if you instantiate an EM instance manually and do not perform the injeciton. Basically, the suggestion you make later in this email is actually already what is being done: a single SharedEventManager instance is injected into every EventManager instance, and each component gets a separate EventManager instance. Please look at Zend\Mvc\Service\EventManagerFactory for details on how the shared version of the SharedEventManager is implemented, and the various factories in that namespace to see how each seeds components with an EventManager pulled from that factory. The ServiceManagerConfiguration class in that same namespace marks the EventManager factory as unshared, which is how we can get discrete instances of the EM into the various components that require one. If you are manually instantiating an object that composes an EM instance, if you have an EM already available, it's very easy to inject the shared manager: $shared = $this->getEventManager()->getSharedManager(); $component = new Some\Component(); $component->getEventManager()->setSharedManager($shared); If you don't do this, yes, you'll fall back to the StaticEventManager. However, in most cases, you likely will be using the ServiceManager for all dependencies. In such cases, your factories will look like this: function ($sm) { $component = new Some\Component(); $component->setEventManager($sm->get('EventManager'); return $component; } This ensures all is wired correcly, and the SharedEventManager is correctly injected. <snip> > I think that it should be highly recommended not to use class names > etc as identifiers, but use aliases instead e.g. 'application'. As for the named contexts the SharedEventManager uses, we decided on class names and interface names as the typical context names, as this makes it easier to know what EM instances are listening to what (just look what object it's composed into). We chose not to use aliases, as aliases are much, much harder to document, and require documentation to understand what they refer to (class names and interface names are self-explanatory and thus self-documenting). > If the Shared Event Managers do end up playing more nicely together > (e.g the MVC Application Shared Event Manager is the same one that the > other Event Managers use), then it might be worth considering removing > the 'identifier' completely and just have unique event names (this > could assume that an Event Manager should be aware of all the events > that it supports?). Another reason supporting this is, if the MVC > Application Event Manager is shared by all, then the identifier param > in its Shared Event Manager becomes redundant? No. This means the event names have to be unique per component triggering events. This gets very limiting very quickly, or requires a ton of extra work to wire an event to listen to every possible object. As an example, right now, you can attach a listener to the SharedEventManager using the context "Zend\Stdlib\DispatchableInterface", and any controller that registers an EM instance with that context -- which any controller extending from the abstract controllers we ship do currently -- will be able to trigger an event that listener will be able to react to. If we had per-class event names, the listeners either need to know every possible class in the application they should listen to, or the objects triggering events all have to use the same exact event names. This latter situation is limiting, because then a listener cannot choose to listen to an event triggered by one controller, but not another. Long story short: The current situation answers a ton of different use cases, and we're not going to change the design at this point. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc |
|
On Sat, Jun 30, 2012 at 6:13 PM, Matthew Weier O'Phinney
<[hidden email]> wrote: > Please look at Zend\Mvc\Service\EventManagerFactory for details on how > the shared version of the SharedEventManager is implemented, I overlooked that the Service\EventManagerFactory can be resused, however that still leaves the 'identifiers' to be initialized by other means? That's not so obvious from the factory alone. > function ($sm) { > $component = new Some\Component(); > $component->setEventManager($sm->get('EventManager'); > return $component; > } > > This ensures all is wired correcly, and the SharedEventManager is > correctly injected. But in the above the 'EventManager' is now assigned new identifiers? And/or new identifiers may have to be added? > We chose not to use aliases, as > aliases are much, much harder to document, and require documentation to > understand what they refer to (class names and interface names are > self-explanatory and thus self-documenting). My concern there is that if the EventManager (or component) changes via the Service Locator then the identifiers may automatically change if they are dynamically determined by the current class name? > No. This means the event names have to be unique per component > triggering events. Isn't that required now, I thought that for any given EM the event names have to be unique? > As an example, right now, you can attach a listener to the > SharedEventManager using the context > "Zend\Stdlib\DispatchableInterface", <snip> > This latter situation is > limiting, because then a listener cannot choose to listen to an event > triggered by one controller, but not another. I'll have to look into that to understand more. > Long story short: The current situation answers a ton of different use > cases, and we're not going to change the design at this point. I'm merely seeking how it might be possible to make it simpler to use without requiring too much knowledge of the underlying code and not to remove what it can do, e.g Shared can mean different things depending on how that component is initialized. |
|
Administrator
|
-- Greg <[hidden email]> wrote
(on Saturday, 30 June 2012, 09:48 PM -0500): > On Sat, Jun 30, 2012 at 6:13 PM, Matthew Weier O'Phinney > <[hidden email]> wrote: > > Please look at Zend\Mvc\Service\EventManagerFactory for details on how > > the shared version of the SharedEventManager is implemented, > > I overlooked that the Service\EventManagerFactory can be resused, > however that still leaves the 'identifiers' to be initialized by other > means? That's not so obvious from the factory alone. It's up to each class receiving an EM instance to assign identifiers. If you look in the various EM-aware classes in the framework, they do so inside setEventManager(). This is the appropriate place to do so. The class should tell the EM what contexts it is interested in. > > function ($sm) { > > $component = new Some\Component(); > > $component->setEventManager($sm->get('EventManager'); > > return $component; > > } > > > > This ensures all is wired correcly, and the SharedEventManager is > > correctly injected. > > But in the above the 'EventManager' is now assigned new identifiers? > And/or new identifiers may have to be added? See above. > > We chose not to use aliases, as aliases are much, much harder to > > document, and require documentation to understand what they refer to > > (class names and interface names are self-explanatory and thus > > self-documenting). > > My concern there is that if the EventManager (or component) changes > via the Service Locator then the identifiers may automatically change > if they are dynamically determined by the current class name? No, see above. > > No. This means the event names have to be unique per component > > triggering events. > > Isn't that required now, I thought that for any given EM the event > names have to be unique? They are unique within the _class_, but not unique within the _framework_. This ensures the granularity of attachment I described previously in the thread. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc |
|
In following the existing code, to me it seems a little easier if there is
class SharedEventManagerFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { return new SharedEventManager(); } } Which the EventManagerFactory would use class EventManagerFactory implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) { $em = new EventManager(); $em->setSharedManager($serviceLocator->get('SharedEventManager')); return $em; } } This would mean that we would remove the need of having to access (or find) and em in order to add a shared listener. E.g in a module bootstrap: public function onBootstrap($event) { $em = $event->getApplication()->getServiceManager()->get('SharedEventManager'); $em->attach('member', 'addMember', function() { var_dump(__LINE__.' '.__FILE__); }); } At this point, I would be considering whether it would be worthwhile adding a 'getSharedEventManager' method to the Application class. The somewhat point here is that for some, we're skipping the whole eduction of the EventManager, and making it easy to add a listener to be added. However at this point, for me at least, is where things start to revert back to becoming complicated. A question in my mind here, is why wouldn't the shared event manager then be able to trigger events - this implies the user really doesn't care how the listeners are contained, e.g. $em->trigger('member', 'addMember'); In summarizing my thoughts, it should be easy as //attach listener $em->attach('member.addMember', function() { var_dump(__LINE__.' '.__FILE__); }); //trigger listeners $em->trigger('member.addMember'); or if you want to listen to an event from a specific object, something of the following nature $em->attach( 'member.addMember', function() { var_dump(__LINE__.' '.__FILE__); }, 'Application\Controller\IndexController' ); $em->trigger('member.addMember', $this); Thanks. On Sun, Jul 1, 2012 at 11:10 AM, Matthew Weier O'Phinney <[hidden email]> wrote: > -- Greg <[hidden email]> wrote > (on Saturday, 30 June 2012, 09:48 PM -0500): >> On Sat, Jun 30, 2012 at 6:13 PM, Matthew Weier O'Phinney >> <[hidden email]> wrote: >> > Please look at Zend\Mvc\Service\EventManagerFactory for details on how >> > the shared version of the SharedEventManager is implemented, >> >> I overlooked that the Service\EventManagerFactory can be resused, >> however that still leaves the 'identifiers' to be initialized by other >> means? That's not so obvious from the factory alone. > > It's up to each class receiving an EM instance to assign identifiers. If > you look in the various EM-aware classes in the framework, they do so > inside setEventManager(). This is the appropriate place to do so. The > class should tell the EM what contexts it is interested in. > >> > function ($sm) { >> > $component = new Some\Component(); >> > $component->setEventManager($sm->get('EventManager'); >> > return $component; >> > } >> > >> > This ensures all is wired correcly, and the SharedEventManager is >> > correctly injected. >> >> But in the above the 'EventManager' is now assigned new identifiers? >> And/or new identifiers may have to be added? > > See above. > >> > We chose not to use aliases, as aliases are much, much harder to >> > document, and require documentation to understand what they refer to >> > (class names and interface names are self-explanatory and thus >> > self-documenting). >> >> My concern there is that if the EventManager (or component) changes >> via the Service Locator then the identifiers may automatically change >> if they are dynamically determined by the current class name? > > No, see above. > >> > No. This means the event names have to be unique per component >> > triggering events. >> >> Isn't that required now, I thought that for any given EM the event >> names have to be unique? > > They are unique within the _class_, but not unique within the > _framework_. This ensures the granularity of attachment I described > previously in the thread. > > -- > Matthew Weier O'Phinney > Project Lead | [hidden email] > Zend Framework | http://framework.zend.com/ > PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- Greg |
|
Administrator
|
-- Greg <[hidden email]> wrote
(on Sunday, 01 July 2012, 12:52 PM -0500): > In following the existing code, to me it seems a little easier if > there is > > class SharedEventManagerFactory implements FactoryInterface > { > public function createService(ServiceLocatorInterface $serviceLocator) > { > return new SharedEventManager(); > } > } > > Which the EventManagerFactory would use > > class EventManagerFactory implements FactoryInterface > { > public function createService(ServiceLocatorInterface $serviceLocator) > { > $em = new EventManager(); > $em->setSharedManager($serviceLocator->get('SharedEventManager')); > return $em; > } > } I agree on this count, and actually already have it in my todo list; I'll get a PR ready today. > This would mean that we would remove the need of having to access (or > find) and em in order to add a shared listener. > > E.g in a module bootstrap: > > public function onBootstrap($event) > { > $em = $event->getApplication()->getServiceManager()->get('SharedEventManager'); Actually, to be honest, previously I would have written: $app = $event->getTarget(); $em = $app->getEventManager(); $shared = $em->getSharedManager(); Still three levels of chaining, though. The above notation _feels_ semantically more correct to me, as it's the same pattern I'd use elsewhere when I don't have access to the service manager -- I'd pull the shared event manager out of the composed event manager. Either way works, though. > $em->attach('member', 'addMember', function() { > var_dump(__LINE__.' '.__FILE__); > }); > } > > At this point, I would be considering whether it would be worthwhile > adding a 'getSharedEventManager' method to the Application class. The > somewhat point here is that for some, we're skipping the whole > eduction of the EventManager, and making it easy to add a listener to > be added. This seems reasonable, but I worry a little about having too many accessors on the Application instance. That said, the Application instance's primary responsibilities are marshalling services and wiring events, so having an accessor for the shared manager makes a degree of sense. > However at this point, for me at least, is where things start to > revert back to becoming complicated. A question in my mind here, is > why wouldn't the shared event manager then be able to trigger events - > this implies the user really doesn't care how the listeners are > contained, e.g. $em->trigger('member', 'addMember'); The shared manager is not supposed to trigger events. It's supposed to simply aggregate listeners. The point is to provide a well-known object you can register against when you do not have access to the object that will be triggering the event. So, as an example: we want to register a listener against the "dispatch" event of _controllers_ (not the Application) in order to determine which layout to use. This listener will examine the target (a controller) for existence of a particular property, and then modify the template of the event's view model accordingly: $shared->attach('Zend\Stdlib\Dispatchable', 'dispatch', function ($e) { $controller = $e->getTarget(); if (!isset($controller->layout)) { // no layout property, not interested return; } $layoutModel = $e->getViewModel(); $layoutModel->setTemplate($controller->layout); }); We don't particularly care which controller we attach to, and we certainly won't have an instance. However, we _do_ care that the target _is_ a controller -- which is why we specify the context (the first argument). Only objects that set an identifier of 'Zend\Stdlib\Dispatchable' on their composed EM instance will trigger this. Now, notice I said the "dispatch" event of controllers, and not the Application. They _both_ trigger a dispatch event -- one is triggered within the other. If we had a single EM instance that we pushed everywhere, this becomes more difficult -- they'd have to have distinct names. And it would mean restricting functionality: If we have a common event name on controllers, how can we attach to a single controller or subset of controllers, without requiring an instance (or instances) of the controller(s)? Let's continue with the subject of event naming. Right now, context identifiers and event names are predictable. Our convention with context identifiers is class names and interface names, while the convention with event names is the name of the method triggering the event. If we have a single, shared event manager instance everywhere, how do we name the events? The examples you use are using arbitrary strings -- these then need to be documented somewhere, so that developers can look up and see who triggers what. If we went a conventions-based approach, we run into the issue that one class might trigger one name, and another derivative class another -- which means listeners now have to attach to both individually -- more boilerplate. I appreciate the suggestions you're making -- but I actually think they'd sacrifice a lot of functionality and create more complexity in the end. -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc |
|
On Sun, Jul 1, 2012 at 3:43 PM, Matthew Weier O'Phinney
<[hidden email]> wrote: > If we had a single EM instance that we pushed everywhere, this becomes > more difficult -- they'd have to have distinct names. No more different than the concatenation of $identifier.'.'.$event_name. It is also not much more different than the steps that an EventManager would have to take in order to ensure its identifiers are distinct. > And it would mean > restricting functionality: If we have a common event name on > controllers, how can we attach to a single controller or subset of > controllers, without requiring an instance (or instances) of the > controller(s)? The example demonstrates the problem if the identifiers were the same and not distinct. Even so, the following still work: $em->attach( 'member.addMember', function() { var_dump(__LINE__.' '.__FILE__); }, 'Application\Controller\IndexController' ); The listener is additionally specifying which sources it is only interested in. > > Let's continue with the subject of event naming. Right now, context > identifiers and event names are predictable. Which can also be determined from $this. > Our convention with context > identifiers is class names and interface names, while the convention > with event names is the name of the method triggering the event. If we > have a single, shared event manager instance everywhere, how do we name > the events? The examples you use are using arbitrary strings -- these > then need to be documented somewhere, so that developers can look up and > see who triggers what. Ideally not completely arbitrary, e.g without vendor name, documented, and emitted by $this. On the other hand, the Event Managers are/can also create arbitrary identifiers which probably would have to be documented. $this gives all this information. > If we went a conventions-based approach, we run > into the issue that one class might trigger one name, and another > derivative class another -- which means listeners now have to attach to > both individually -- more boilerplate. Not if you explicitly specify $this? At least to some degree. The more distinctly definitive the event name, the closer it is to targeting listeners without requiring specific knowledge of the source from which the event is being emitted? E.g mvc.bootstrap mvc.controller.bootstrap > I appreciate the suggestions you're making -- but I actually think > they'd sacrifice a lot of functionality and create more complexity in > the end. In my example, $this provides the same information that would be provided to EventManagers as identifiers. However some consequences come to mind: - Identifiers are computationally determined from the source object (i.e. not configured or hard coded). - Should it be possible for one component to access another components event manager and trigger listeners - at which point we still would not know the originating object. For example $event = new MvcEvent(); $event->setName('mvc.bootstrap') ->setTarget($this) ->setApplication($this) ->setRequest($this->getRequest()) ->setResponse($this->getResponse()) ->setRouter($serviceManager->get('Router')); $em->trigger($event); Here the target is the source object emitting the event, and the Shared Event Manager could iterate through all the listeners for that event name. If the registered listeners provide additional information about which objects (class and interface names) that it additionally wants to target, then the event manager would only call that listener if there is a match. Another example might be: $em->attach( 'mvc.controller.bootstrap', function() {}, //callback function($source) {} //match event source }); And to attach a listener to all events from a particular object: $em->attach(null, function() {}, function($source) { .... match $source .... }); To summarize a SharedEventManager could trigger listeners as long as $this is passed. $this would replace the necessity of each component having an event manager and the identifiers would be determined from the class and interface names of $this. Allowing the Shared Manager to trigger events in this way would just be an alternative to the existing methodology. I appreciate you walking me through this, I now better understand the derivation of the EventManager. |
|
Administrator
|
Greg --
I see what some of your points are. However, at this time, we're circling in on beta5, and planning RC status following that. The design we have right now is well-tested, and covers the variety of use cases required by the framework; further, I've not seen many issues raised against it -- either technically or usability-wise -- particularly in recent months. As such, I'm unlikely to make changes at this point. If you're dissatisfied with the current design, I suggest you start working up an alternate implementation, testing it in applications, and then proposing it for the 3.0 release. Based on community feedback, we plan to do major releases every 18-24 months so as to minimize the amount of refactoring, and thus backwards incompatible breakage, done in any given major release. As such, if the solution you develop offers clear advantages over what we ship in 2.0, you won't have to wait a tremondously long time before seeing it in a released version. Thanks for taking the time to analyze the component in depth! -- Greg <[hidden email]> wrote (on Sunday, 01 July 2012, 10:32 PM -0500): > On Sun, Jul 1, 2012 at 3:43 PM, Matthew Weier O'Phinney > <[hidden email]> wrote: > > If we had a single EM instance that we pushed everywhere, this becomes > > more difficult -- they'd have to have distinct names. > > No more different than the concatenation of $identifier.'.'.$event_name. > > It is also not much more different than the steps that an EventManager > would have to take in order to ensure its identifiers are distinct. > > > And it would mean > > restricting functionality: If we have a common event name on > > controllers, how can we attach to a single controller or subset of > > controllers, without requiring an instance (or instances) of the > > controller(s)? > > > The example demonstrates the problem if the identifiers were the same > and not distinct. Even so, the following still work: > > $em->attach( > 'member.addMember', > function() { > var_dump(__LINE__.' '.__FILE__); > }, > 'Application\Controller\IndexController' > ); > > > The listener is additionally specifying which sources it is only interested in. > > > > > Let's continue with the subject of event naming. Right now, context > > identifiers and event names are predictable. > > > Which can also be determined from $this. > > > > Our convention with context > > identifiers is class names and interface names, while the convention > > with event names is the name of the method triggering the event. If we > > have a single, shared event manager instance everywhere, how do we name > > the events? The examples you use are using arbitrary strings -- these > > then need to be documented somewhere, so that developers can look up and > > see who triggers what. > > Ideally not completely arbitrary, e.g without vendor name, documented, > and emitted by $this. > > On the other hand, the Event Managers are/can also create arbitrary > identifiers which probably would have to be documented. $this gives > all this information. > > > If we went a conventions-based approach, we run > > into the issue that one class might trigger one name, and another > > derivative class another -- which means listeners now have to attach to > > both individually -- more boilerplate. > > Not if you explicitly specify $this? At least to some degree. > > The more distinctly definitive the event name, the closer it is to > targeting listeners without requiring specific knowledge of the source > from which the event is being emitted? > > E.g > > mvc.bootstrap > mvc.controller.bootstrap > > > > I appreciate the suggestions you're making -- but I actually think > > they'd sacrifice a lot of functionality and create more complexity in > > the end. > > In my example, $this provides the same information that would be > provided to EventManagers as identifiers. > > However some consequences come to mind: > > - Identifiers are computationally determined from the source object > (i.e. not configured or hard coded). > - Should it be possible for one component to access another components > event manager and trigger listeners - at which point we still would > not know the originating object. > > For example > > $event = new MvcEvent(); > > $event->setName('mvc.bootstrap') > ->setTarget($this) > ->setApplication($this) > ->setRequest($this->getRequest()) > ->setResponse($this->getResponse()) > ->setRouter($serviceManager->get('Router')); > > > $em->trigger($event); > > Here the target is the source object emitting the event, and the > Shared Event Manager could iterate through all the listeners for that > event name. If the registered listeners provide additional information > about which objects (class and interface names) that it additionally > wants to target, then the event manager would only call that listener > if there is a match. > > Another example might be: > > $em->attach( > 'mvc.controller.bootstrap', > function() {}, //callback > function($source) {} //match event source > }); > > And to attach a listener to all events from a particular object: > > $em->attach(null, function() {}, function($source) { .... match $source .... }); > > To summarize a SharedEventManager could trigger listeners as long as > $this is passed. $this would replace the necessity of each component > having an event manager and the identifiers would be determined from > the class and interface names of $this. > > Allowing the Shared Manager to trigger events in this way would just > be an alternative to the existing methodology. > > I appreciate you walking me through this, I now better understand the > derivation of the EventManager. > -- Matthew Weier O'Phinney Project Lead | [hidden email] Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc |
| Powered by Nabble | Edit this page |
