A few questions need to be clarified first:
Q1. What is an event?
A: An event is a named behavior. When this behavior occurs, the event is said to be triggered.
Q2. What is a monitor?
A: The listener determines the logical expression of the event and is triggered by the event. Listeners and events are often paired. Of course, one event can also correspond to multiple listeners. Listeners react to events. When an event is triggered, it is up to the listener to react. This way, the firing of multiple events can cause a single listener to react. An event can also have multiple listeners reacting to it. (In a word: the relationship between listeners and events can be either one-to-many or many-to-one)
Q3. What is the event manager used for?
A: Event Manager (EventManager), as you can tell from the name, is used to manage events. But how does he manage it? Event managers often aggregate multiple listeners for multiple events (the events and listeners here are both indefinite [that is, it can be one or multiple]). The event manager is also responsible for triggering events.
Generally speaking, we use objects to represent events. An event object describes the basic elements of an event, including when and how the event is triggered.
About the basic elements of an event: event name, target (the object that triggers the event, usually the event object itself), and event parameters. We have said before that an event is equivalent to a behavior. In programs, we often use methods or functions to represent behavior. Therefore, event parameters are often also function parameters.
Also about Shared managers: As mentioned before, one event can target multiple listeners. This is achieved through Shared managers. The implementation of EventManager includes (combines) SharedEventManagerInterface [using code injection in the constructor or setSharedManager, please view the source code for details]), and SharedEventManagerInterface describes an object that aggregates listeners. These listeners are only connected to objects with specified identities. symbol event. SharedEventManager does not trigger events, it only provides listeners and connects to events. EventManger obtains listeners with specific identifiers by querying SharedEventMangaer.
Several important behaviors in EventManager:
1. Create an event: Creating an event is actually just creating an instance of EventManagerInterface
2. Trigger event: Generally, trigger is used in event behavior, so that the event can be triggered directly when we execute the behavior. Function prototype: trigger($eventName,$target=null,$argv=[]); $eventName is generally the time action name (often replaced by __FUNCTION__), $target is the event object itself and can be replaced by $this, and $argv is The parameters of the incoming event (generally the parameters of the event behavior).
Of course, there is not only one way to trigger events: trigger, but also triggerUntil, triggerEvent, and triggerEventUntil. From the name, we can see that it is divided into two categories: trigger and triggerEvent; the trigger class only simply triggers events, and does not need to be implemented to create an event instance. It only needs an event name, and trigger not only triggers events but also triggers listeners. , requires an event instance. Methods with the Until suffix require a callback function, and the results of each listener will be passed to the callback function. If the callback function returns a true bool value, the EventManager must short-circuit the listener. (For short circuit, see short circuit below)
For more information, please check the official API or the specific comments of EventMangerInterface.
3. Create a listener and connect to the event :
Listeners can be created through EventManager or SharedEventManager. Both use the attach method, but the parameters are slightly different.
Let’s look at the EventManager method first:
Method prototype: attach($eventName, callable $listener, $priority = 1)
It’s very simple, we only need the event name, and a callable function, and finally the default priority is 1 (the priorities of the built-in events in zend are mostly negative numbers, so if you want to customize the priority of the listener If it is relatively high, just assign a positive number directly.)
The callable function is our listener. There is a special case for event names: "*". This is similar to regular matching, connecting all events to this listener.
Let’s look at the SharedEventManager method now:
Method prototype: attach($identifier, $eventName, callable $listener, $priority = 1);
The only difference from before is that there is an additional identifier parameter. The source code comments about identifier are as follows:
used to pull shared signals from SharedEventManagerInterface instance;
Used to pull the sharing signal from the SharedEventmanager instance. identifier is an array. According to my understanding: if an event (note that SharedEventmanager cannot create events) defines an identifier, it means that the event is shareable. Let the SharedEventManger instance pass in the identifier parameter when creating the listener using attach. EventManager can use the identifier parameter to query all listeners.
What’s confusing is that since we have the event name, we can query the relevant listeners through the event name, so why bother adding the identifier attribute? What I am considering is the issue of event inheritance: Suppose there is an event class Foo that contains an event behavior act. SubFoo inherits the Foo class and overrides the event behavior act inside. The event actions of both classes have the same event name act. At this time, if you query the listener by the event name, there will obviously be a conflict. At this time, we define identifier[__CLASS__, get_class($this)] and specify the identifier as SubFoo in the listener, which will obviously match the event behavior act in the SubFoo class.
Above we can listen to multiple events through SharedEventManager, and we can also implement it through listener aggregates. Through ZendEventManagerListenerAggregateInterface, let a class listen to multiple events and connect one or more instance methods as listeners. The same interface also defines attach(EventManagerInterface $events) and detach(EventManagerInterface $events). In the specific implementation of attach, we use the attach method of the EventManager instance to listen to multiple events.
<span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\EventInterface; </span><span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\EventManagerInterface; </span><span style="color: #0000ff;">use</span><span style="color: #000000;"> Zend\EventManager\ListenerAggregateInterface; </span><span style="color: #0000ff;">use</span> Zend\<span style="color: #008080;">Log</span><span style="color: #000000;">\Logger; </span><span style="color: #0000ff;">class</span> LogEvents <span style="color: #0000ff;">implements</span><span style="color: #000000;"> ListenerAggregateInterface { </span><span style="color: #0000ff;">private</span> <span style="color: #800080;">$listeners</span> =<span style="color: #000000;"> []; </span><span style="color: #0000ff;">private</span> <span style="color: #800080;">$log</span><span style="color: #000000;">; </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> __construct(Logger <span style="color: #800080;">$log</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>-><span style="color: #008080;">log</span> = <span style="color: #800080;">$log</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> attach(EventManagerInterface <span style="color: #800080;">$events</span><span style="color: #000000;">) { </span><span style="color: #800080;">$this</span>->listeners[] = <span style="color: #800080;">$events</span>->attach('do', [<span style="color: #800080;">$this</span>, 'log'<span style="color: #000000;">]); </span><span style="color: #800080;">$this</span>->listeners[] = <span style="color: #800080;">$events</span>->attach('doSomethingElse', [<span style="color: #800080;">$this</span>, 'log'<span style="color: #000000;">]); } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> detach(EventCollection <span style="color: #800080;">$events</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$this</span>->listeners <span style="color: #0000ff;">as</span> <span style="color: #800080;">$index</span> => <span style="color: #800080;">$listener</span><span style="color: #000000;">) { </span><span style="color: #800080;">$events</span>->detach(<span style="color: #800080;">$listener</span><span style="color: #000000;">); </span><span style="color: #0000ff;">unset</span>(<span style="color: #800080;">$this</span>->listeners[<span style="color: #800080;">$index</span><span style="color: #000000;">]); } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> <span style="color: #008080;">log</span>(EventInterface <span style="color: #800080;">$e</span><span style="color: #000000;">) { </span><span style="color: #800080;">$event</span> = <span style="color: #800080;">$e</span>-><span style="color: #000000;">getName(); </span><span style="color: #800080;">$params</span> = <span style="color: #800080;">$e</span>-><span style="color: #000000;">getParams(); </span><span style="color: #800080;">$this</span>-><span style="color: #008080;">log</span>->info(<span style="color: #008080;">sprintf</span>('%s: %s', <span style="color: #800080;">$event</span>, json_encode(<span style="color: #800080;">$params</span><span style="color: #000000;">))); } }</span>
Benefits of using Aggregate:
1. Allows you to use stateful listeners
2. Combine multiple similar listeners in a single class and connect them all at once
The results returned by the introspection listener
We have a listener, but how do we receive the results it returns? The default implementation of EventManager returns an instance of ResponseCollection. This class inherits from PHP's SplStack. The basic structure is a stack, so it allows you to traverse Responses in reverse order.
ResponseCollection provides several useful methods:
first(): Get the first result
last(): Get the last result
contains($value): Check whether the stack contains a certain value. If it does, return true, otherwise false.
Short loop listener execution:
What is a short circuit? Suppose you want to do something until it has a result. This is a loop. If you know the result of this thing in advance (for example, you have done this thing before), then you don't have to complete it completely. At this time, you only need to execute a short loop.
We have a caching mechanism when adding EventManager. Trigger an event in a method and return directly if we find a cached result. If the cached result is not found, we cache the triggered event for later use. In fact, it is the same as the cache in computer hardware.
The EventManager component provides two processing methods: 1. triggerUntil(); 2. triggerEventUntil. Both methods accept a callback function as the first parameter. If the callback function returns true, execution stops.
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> someExpensiveCall(<span style="color: #800080;">$criteria1</span>, <span style="color: #800080;">$criteria2</span><span style="color: #000000;">) { </span><span style="color: #800080;">$params</span> = <span style="color: #008080;">compact</span>('criteria1', 'criteria2'<span style="color: #000000;">); </span><span style="color: #800080;">$results</span> = <span style="color: #800080;">$this</span>->getEventManager()-><span style="color: #000000;">triggerUntil( </span><span style="color: #0000ff;">function</span>(<span style="color: #800080;">$r</span><span style="color: #000000;">){ </span><span style="color: #0000ff;">return</span> (<span style="color: #800080;">$r</span><span style="color: #000000;"> instanceof SomeResultClass); }</span>, <span style="color: #ff00ff;">__FUNCTION__</span>, <span style="color: #800080;">$this</span>, <span style="color: #800080;">$params</span><span style="color: #000000;"> ); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$results</span>-><span style="color: #000000;">stopped()) { </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$results</span>->last()'<span style="color: #000000;"> } }</span>
From the above example, we know that if execution stops, it is most likely because the last result in the stack meets our requirements. In this way, we only need to return the result, why do we need to perform extra calculations?
The processing stops execution in the event, and we can also stop execution in the listener. The reason is that we have received a certain event before, and now that we have received the same event again, we can use the previous results as a matter of course. In this case, the listener calls stopPropagation(true), and the EventManager returns directly without notifying additional listeners.
<span style="color: #800080;">$events</span>->attach('do', <span style="color: #0000ff;">function</span>(<span style="color: #800080;">$e</span><span style="color: #000000;">) { </span><span style="color: #800080;">$e</span>-><span style="color: #000000;">stopPropagation(); </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> SomeResultClass(); });</span>
当然,使用触发器范例可能会导致歧义,毕竟你并不知道最终的结果是否满足要求。
Keeping it in order.
偶尔你会关心监听器的执行顺序。我们通过监听器的优先级来控制执行顺序(上面说讲的短回路也会影响执行顺序)。每一个EventManager::attach()和SharedEventManager::attach()都会接受一个而外的参数:priority。默认情况下为1,我们可以省略该参数。如果你提供了该参数:高优先级执行的早,低优先级的可能会推迟执行。
自定义事件对象:
我们之前使用trigger()触发事件,在这同时我们也创建了事件。但trigger()的参数有限,我们只能指定事件的对象,参数,名称。实际上我们可以创建一个自定义事件,在Zendframework里面有个很重要的事件:MvcEvent。很显然MvcEvent便是一个自定义事件,该事件组合了application实例,路由器,路由匹配对象,请求和应答对象,视图模型还有结果。我们查看MvcEvent的源码会发现MvcEvent类实际上继承了Event类。同理我们的自定义事件对象也可以继承Event类或者继承MvcEvent。
<span style="color: #800080;">$event</span> = <span style="color: #0000ff;">new</span><span style="color: #000000;"> CustomEvent(); </span><span style="color: #800080;">$event</span>->setName('foo'<span style="color: #000000;">); </span><span style="color: #800080;">$event</span>->setTarget(<span style="color: #800080;">$this</span><span style="color: #000000;">); </span><span style="color: #800080;">$event</span>->setSomeKey(<span style="color: #800080;">$value</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">injected with event name and target:</span> <span style="color: #800080;">$events</span>->triggerEvent(<span style="color: #800080;">$event</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">Use triggerEventUntil() for criteria-based short-circuiting:</span> <span style="color: #800080;">$results</span> = <span style="color: #800080;">$events</span>->triggerEventUntil(<span style="color: #800080;">$callback</span>, <span style="color: #800080;">$event</span>);
上面的代码可以看到我们使用自定义事件类创建了一个事件对象,调用相关拦截器为事件对象设置属性。我们有了事件对象还是用trigger()触发事件吗?显然不是,我们使用triggerEvent($event)方法,该方法接收一个事件对象。而triggerEventUntil有一个回调函数,该回调函数作为是否进行短回路的依据。