简介
Aura信号包是SignalSlots/EventHandler的PHP 5.3+实现。有了它,当对象向信号管理器发送信号(“通知”
或“事件”)时,我们就可以调用处理器(“slots”或“hooks”)。
基本用法
实例化信号管理器
首先实例化信号管理器Manager类。最简单的方法就是调用aura.signal/scripts/instance.php脚本。
<?php $signal = require '/path/to/aura.signal/scripts/instance.php';
添加信号处理器
在向信号管理器Manager发送信号之前,我们要先添加一个处理器。具体的步骤如下:
- 等待发送信号的类。可以是
'*'代表“任意类”,也可以是一个类的全称。 - 信号名称。
- 处理信号的闭包或回调。
例如,添加一个闭包,让其在vendor\package\Example类每次发送'example_signal'信号都执行一次:
<?php $signal->handler( 'vendor\package\Example', 'example_signal', function ($arg) { echo $arg; } );
类发送信号
要发送信号,该类首先必须有一个Manager实例。然后再调用send()方法,并向其传递三个参数:类自身的实例、要发送的信号以及传递给信号处理器的参数。
例如,我们将定义vendor\package\Example类,并使用它向Manager发送信号。
<?php namespace vendor\package; use aura\signal\Manager as SignalManager; class Example { protected $signal; public function __construct(SignalManager $signal) { $this->signal = $signal; } public function doSomething($text) { echo $text; $this->signal->send($this, 'example_signal', $text); } }
当我们调用doSomething()方法时,它将会向Manager发送'example_signal'信号,并且Manager会为该信号调用相应的处理器。
信号继承
如果某个类发送一个信号,但是并没有为该信号设置任何信号处理器,那么Manager就不会有任何响应。然而,如果曾为该类的父类设置过信号处理器,那么当其子类发送父类曾处理过的信号时,Manager将会以相同的方式处理子类发送的信号。
例如,我们定义两个类,并调用它们的doSomethingElse()方法 。。。
<?php namespace vendor\package; use aura\signal\Manager as SignalManager; class ExampleChild extends Example { public function doSomethingElse($text) { echo $text . $text . $text; $this->signal->send($this, 'example_signal', $text) } } class ExampleOther { protected $signal; public function __construct(SignalManager $signal) { $this->signal = $signal; } public function doSomethingElse($text) { echo $text . $text . $text; $this->signal->send($this, 'example_signal', $text) } }
。。。那么Manager会处理来自ExampleChild的信号,因为它的父类定义过该信号的处理器。而Manager不会处理ExampleOther发送的信号,因为它或它的父类都没有在Manager中添加信号处理器。
对象发送信号
当然,你也可以给对象实例绑定信号处理器,这样仅当该对象发出的信号才会被处理。实现很简单,只要将传递给信号处理器的参数$sender设为对象实例即可。
<?php /** * @var aura\signal\Manager $signal */ $object = new vendor\package\ExampleChild($signal); $signal->handler( $object, 'example_signal', function ($arg) { echo "$arg!!!";} );
如果指定对象实例发送example_signal信号,那么信号处理器便会被激活。即使ExampleChild类的其他实例发送同样的信号也不会激活信号处理器。当你需要在对象内部设置信号处理器且回调函数也是对象内部方法时,这就变得异常有用了。例如:
<?php namespace vendor\package; use aura\signal\Manager as SignalManager; class ExampleAnotherChild extends Example { public function __construct(SignalManager $signal) { parent::__construct(); $this->signal->handler($this, 'preAction', array($this, 'preAction')); $this->signal->handler($this, 'postAction', array($this, 'postAction')); } public function action() { $this->signal->send($this, 'preAction'); $this->doSomething(); $this->signal->send($this, 'postAction'); } public function preAction() { // happens before the main action() logic } public function postAction() { // happens after the main action() logic } }
当ExampleAnotherChild::action()方法被调用时,上面的代码做什么事呢?
- 向
Manager发送'preAction'信号,当处理器被激活时将会调用对象的preAction()方法。 - 调用对象的
doSomething()方法。(注意,doSomething()方法向Manager发送自己的'example_signal'信号。) - 向
Manager发送'postAction'信号,当处理顺被激活时针会调用对象的。postAction()方法。
如果存在ExampleAnotherChild类或其父类的信号处理器,它们同样会被执行。也就是说,我们可以结合使用类信号处理器和对象信号处理器。
高级用法
处理器顺序组
默认情况下,所有处理器Handler对象将被添加到Manager栈(译者注:实际上应该是队列)的末尾,并且会按照添加的顺序进行处理。有时候你可能需要以不同的顺序执行Handler,比如在最开始或最后面。如果你有这方面的需求,那么当你向Manager添加Handler的时,再传递一个$position顺序值。(Handler处理器的默认$position顺序是5000。)
<?php // add a closure at position 1000, which means it will be processed // before all handlers at the default position 5000. $closure = function() { echo "Before all others."; }; $signal->handler('ExampleChild', 'example_signal', $closure, 1000); // add a closure at position 9000, which means it will be processed // after all handlers at the default position 5000. $closure = function() { echo "After all others."; }; $signal->handler('ExampleChild', 'example_signal', $closure, 9000);
按指定顺序添加的Handler处理器同样会被追加到同一顺序组。
结果检测
信号发送后,我们可以审查所有处理器对该信号处理的结果。
<?php // send a signal $this->signal->send($this, 'example_signal'); // get the result collection $results = $this->signal->getResults(); // go through each result ... foreach ($results as $result) { // ... and echo the value returned by the Handler callback echo $result->value; }
getResults()方法返回Result对象的集合ResultCollection,每个对象都有以下属性:
$origin:发送信号的对象。$sender:处理器期望的发送者。$signal:$origin发送的信号。$value:Handler处理器回调返回的值。
如果你只想获取最后一个结果,你可以调用ResultCollection对象的getLast()方法:
<?php // send a signal and retain the results from each Handler $results = $this->signal->send($this, 'example_signal'); // get the last result $result = $results->getLast(); // and echo the value returned by the last Handler callback echo $result->value;
中止信号处理
有时候可能不得不中止信号处理过程。如果一个处理器回调返回aura\signal\Manager::STOP常量,那么不会再有其他处理器处理该信号。
首先,我们定义一些处理器;注意第二个处理器返回STOP常量:
<?php // add signal handlers $signal->handler( 'vendor\package\Example', 'mock_signal', function() { return 'first'; } ); $signal->handler( 'vendor\package\Example', 'mock_signal', function() { return \aura\signal\Manager::STOP; } ); $signal->handler( 'vendor\package\Example', 'mock_signal', function() { return 'third'; } );
然后,我们在某个对象内部发送一个信号:
<?php $this->signal->send($this, 'mock_signal'); $results = $this->signal->getResults();
正常情况下,$results数组有三个元素。但是这里它只有两个,因为第二个处理器返回了\aura\signal\Manager::STOP。也由于这个原因,第三个处理器不会执行。你可以调用ResultCollection::isStopped()方法来检测处理器是否被Manager中止。
<?php if ($results->isStopped()) { $result = $results->getLast(); echo "Processing for signal 'mock_signal' stopped " . "by handler for " . $result->sender; }
在构造期设置信号处理器
我们也可以在构造期往Manager中添加处理器Handler。这就允许我们使用一个或多个配置文件为Manager定义Handler栈。
假如有一个配置文件/path/to/signal_handlers.php,代码如下。。。
<?php return array( // first handler, with a closure array( 'vendor\package\Example', 'mock_signal', function() { return 'foo'; }, ), // second handler, with a static callback array( 'vendor\package\Example', 'mock_signal', array('vendor\package\SomeClass', 'someMethod'), ), // third handler, with a closure and position array( 'vendor\package\Example', 'mock_signal', function() { return 'baz'; }, 1000, ), );
。。。我们可以像下面这样配置Manager:
<?php namespace aura\signal; $handlers = require '/path/to/signal_handlers.php'; $signal = new Manager( new HandlerFactory, new ResultFactory, new ResultCollection, $handlers );
这就相当于调用了三次$signal->handler()方法。
呜谢
感谢Richard "Cyberlot" Thomas最初的建议,以及Matthew Weier O’Phinney。