ZF2 events triggering - events

I have the following situation:
I one of the application modules I trigger an event, in the same module I create an event listener which listen to that trigger via the shared event manager system, by this step all works fine, I tried to create more listeners in different modules to that event and they also works fine, all listeners are called.
What I want is to have a system where I can be able to trigger the same event from multiple places. For example, I create a Send Mail module, in which I'll have a listener which will listen to the "sendMail" trigger and will do some actions, is it possible to trigger the same event from different modules?
I tried to trigger the same event from 2 places but the attach method of the shared event takes the first parameter as the id of the caller class, i.e. the id of the class that triggers the event, in such way I can trigger the event only from the specified class.
What I want is to trigger that event from as many places as I want.
Thank you all very much!

The shared event manager allows attaching to wildcards, so to attach to a foo event no matter it's source you would do this:
$sharedEventManager->attach('*', 'foo', ...);
For more details check out the EventManager doc page on wildcards: http://framework.zend.com/manual/current/en/tutorials/tutorial.eventmanager.html#wildcards

If you use the Zend\EventManager\EventManagerAwareTrait or inherit from ZfcBase\EventManager\EventProvider you can supply your own event identifiers to listen to.
If you take a look at the code contained in the EventManagerAwareTrait::setEventManager() method here
https://github.com/zendframework/zf2/blob/master/library/Zend/EventManager/EventManagerAwareTrait.php#L44-L54
and also in ZfcBase\EventManager\EventProvider::setEventManager() here
https://github.com/ZF-Commons/ZfcBase/blob/master/src/ZfcBase/EventManager/EventProvider.php#L26-L37
you'll notice that the method automatically looks for a property named $eventIdentifier, expecting it to be a string or an array of strings, which if present, gets merged with the default identifiers of FQCN and class name.
So basically, you can add your own identifiers by simply adding a property to your event manager aware classes (the ones triggering events)
<?php
namespace Somenamespace;
use Zend\EventManager\EventManagerAwareInterface;
class EventTriggeringClass implements EventManagerAwareInterface
{
use \Zend\EventManager\EventManagerAwareTrait;
protected $eventIdentifier = 'SendMailIdentifier';
}
and
namespace Someothernamespace;
use Zend\EventManager\EventManagerAwareInterface;
class SomeOtherEventTriggeringClass implements EventManagerAwareInterface
{
use \Zend\EventManager\EventManagerAwareTrait;
protected $eventIdentifier = 'SendMailIdentifier';
}
Your listeners would now just need to attach to the SendMailIdentifier rather than target the FQCN of any specific class.
Of course, you can also still attach to the FQCN where necessary, since it's one of the identifiers that was merged by the setEventManager method.

Why do you use the shared event manager?
If you create a service for your "operations" you can initialize a
new eventmanager where you can attach an event whereever you want to.
And ofc pull the trigger from everywhere.
I also would recommend you to attach an event aggregate for easier code review and readability

Related

Laravel: Call function on sync or attach

Whenever I modify a relationship (many to many) I want to do another action. As far as I know this can't be done using event listeners (see https://github.com/laravel/framework/issues/2303). This works,
function setUserGroups($ids){
$this->groups->sync($ids);
doSomethingElse();
}
The downside is that it's not intuitive for other developers to remember to use this function. Generally I'm able to attach behavior to change in other attributes using mutators, defining them as guarded or adding events, and I just want to be able to do something similar with syncing/attaching.
I don't see creating a repository as a solution for this. We're not using the repository pattern in our application and honestly I see this issue coming up there as well.
You may register your custom event handlers and listeners:
// register listener
Event::listen('user.groupsModified', 'User#onGroupsModified');
Event Handler:
// Handle event in User class
function onGroupsModified($event)
{
}
Then fire the event in setUserGroups function:
function setUserGroups($ids){
$this->groups->sync($ids);
// Fire the event
Event->fire('user.groupsModified');
}
This way, you can abstract the dependency from the setUserGroups method, now you only need to fire the event and no need to know the handler's name.

What is the correct place/bundle to place this action

Say I have a CoreBundle, which has an entity called Event. In the CoreBundle, events can e.g. be shown (showAction). I also have a BackendBundle. The event's deleteAction can only be triggered from the backend. However, the deleteAction belongs to an entity that is defined in the CoreBundle. Both the CoreBundle and the BackendBundle have an EventController.
The question is: Should the deleteAction be placed in the BackendBundle's EventController or in the CoreBundle's EventController?
P.s. I know both will work, but this is more somewhat of a phylosophical question.
I would suggest you have a BackendBundle with an EventController and a deleteAction. This deleteAction may call a specific handler (or manager or whatever) inside the CoreBundle, but I would keep the controller code inside the BackendBundle.
First, it makes it easy to follow the code without switching bundles. I can see that the request comes in, that either the entity is deleted or that some manager is called and that a redirect is send or a template is rendered.
Second, and way more important, is that if you introduce another bundle which has a deleteAction for the backend, you can either have different ways of handling them (one inside it's own bundle and one inside the CoreBundle) or you have to name them different and create a big mess.
In generell, I stick to the rule to have the controller inside the same bundle where the route and the view lives and only share the model. In case of a CoreBundle, I handle deletion with a manager between the controller and the model. In your case the deleteAction would get a EventManager service and call a delete with either the object or an id (depending on my needs). This way the code executed to delete an event is in one place and can be changed easily.

Which event should I use just before a page is shown on browser on Plone to trigger a subscriber?

I want to create a subscriber that gets triggered when the user tries to access the resource (which is a custom content-type). So, the object is not being added, modified, nothing, is just being traversed. Something like a Zope View Event.
So, basically, suppose a custom content type has a custom workflow (two states: private and viewed). The initial state is private. This content type is only going to be created programatically, using _createObjectByType by anonymous users. Suppose an object called myobjet was added, programatically, to the root folder of my Plone site.
What I want is: when the user access
http://localhost:8080/Plone/myobject
...it automatically changes the state of the workflow of this object to viewed. The url http://localhost:8080/Plone/myobject is going to be a custom view, not the default base_edit.
Which event should I use? I tried IEndRequestEvent and IBeforeTraverseEvent from this list and none of them work: the handler is not being called for my custom object interface.
I've tried other events with my custom object interface (like IObjectEditedEvent), and, for this event, my handler is called when I edit an object that implements the interface. But using IEndRequestEvent and IBeforeTraverseEvent doesn't call the handler.
IEndRequestEvent and IBeforeTraverseEvent only work when I set the subscriber to all interfaces:
<subscriber
for="*
zope.app.publication.interfaces.IBeforeTraverseEvent"
handler=".subscriber.myhandler"
/>
And when I make myhandler print the object and the event in this situation, it shows:
<PloneSite at Plone>
<zope.app.publication.interfaces.BeforeTraverseEvent object at 0xd52618c>
If the solution is to write an event myself, is there an easy tutorial for this?
You might want to have a look at http://pypi.python.org/pypi/plone.validatehook.
Make sure you bind the event to the right interface. If you bind it to "Interface" (as described on the plone.validatehook pypi page) the event will get called for every single request. In order to restrict the event to contentish objects you can do the following:
from Products.CMFCore.interfaces import IContentish
#adapter(IContentish, IPostValidationEvent)
def RedirectMember(object, event):
...
(Edit: I removed my first answer because it didn't work)
Not sure what this subscriber is supposed to do, but if the object is not being modified, added or whatsoever than I must suspect it will just be viewed...so why not just use the __call__ method of the items view (or the __update__ method if you are using five.grok/dexterity)?

Magento Dispatching and Catching Events

I am dealing with Magento for a while and i find it very interesting and probably my future choice of work tool. Though i have some troubles understanding some of the stuff going on. If i call www.store.com/catalog/product/view/id/2 then the product controller executes from the catalog core module, in it the product is being fetched via _initProduct() method first in which this event is being dispatched:
Mage::dispatchEvent('catalog_controller_product_init_before', array('controller_action'=>$this));.
Which class/method is being called? As i understood that should be a method from observer class which is under Model folder and it should be defined in the etc/config.xml file.
Some of the events defined in the config.xml are executed automatically... (why?) where is defined the one used in the viewAction() from the ProductController.php in Catalog module? How i can send and use array data to the observer's methods, cause i saw some of them contains this method: Mage::app()->reinitStores() which re-inits store, group and website collections and it's not something simple. I find this very powerful and i really want to know the posibilities with using Observers and Events.
Event observers can be defined in the config.xml for any module that is active in the system, they don't necessarily have to be defined in the same module.
You can send data to the event observers by adding information to the event object, which is done in an array defined as the second argument to dispatchEvent. Just add more elements to the array and the event observer method can extract it from $observer->getEvent(). You are also free to define your own events by calling the same dispatchEvent method.
One of the handy things about most Magento models is that they are inherited from the Mage_Core_Model_Abstract class which includes event for _load_after, _save_before, _save_after, _delete_before and _delete_after. For example, the product model has catalog_product_load_after, catalog_product_save_before, etc.
Hope that gives you some more information about the possibilities of using events.

How do I listen to all Seam contextual events with parameterized names?

Seam will fire different kinds of events that relate to particular scopes, tasks, or processes and appends the name of the scope, task or process to the end of the event.
How do I listen to all the events of a type?
E.g. for any <name> I'd like to listen to events such as these:
org.jboss.seam.createProcess.<name> — called when the process is created
org.jboss.seam.endProcess.<name> — called when the process ends
org.jboss.seam.initProcess.<name> — called when the process is associated with the conversation
org.jboss.seam.startTask.<name> — called when the task is started
org.jboss.seam.endTask.<name> — called when the task is ended
I need to do this despite not knowing the list of valid names up front... :-(
I hope to be using #Observer to create the observer, or something similar, and I'll listen to up to two event classes in the same component.
You can easily do this by replacing Seam's Events class with your own implementation. Then look for events that are raised that start with a particular string:
#Scope(ScopeType.STATELESS)
#BypassInterceptors
#Name("org.jboss.seam.core.events")
#Install(precedence=APPLICATION)
public class Events extends org.jboss.seam.core.Events
{
#Override
public void raiseEvent(String type, Object... parameters )
{
super.raiseEvent( type, parameters );
if ( type.startsWith( "org.jboss.seam.createProcess" ) )
{
super.raiseEvent( "org.jboss.seam.createProcess", parameters );
}
//etc.
}
}
You can now observe "org.jboss.seam.createProcess" to get all createProcess events.
Inside the if, you must write super.raiseEvent(...) otherwise you'll get an infinite loop.

Resources