aura / signal
SignalSlots/EventHandler 实现方案;通过它,我们可以在对象向信号管理器发送信号(通知或事件)时调用处理程序(槽或钩子)。
Requires
- php: >=5.4.0
- aura/installer-default: 1.0.*
README
Aura Signal 包是 PHP 的 SignalSlots/EventHandler 实现。通过它,我们可以在对象向信号管理器发送信号(通知或事件)时调用处理程序(槽或钩子)。
此包符合 PSR-0、PSR-1 和 PSR-2 规范。如果您发现不符合规范的地方,请通过 pull request 发送补丁。
基本用法
实例化信号管理器
首先,实例化信号 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
的信号,因为其父类有一个处理程序。对于 ExampleOther
,Manager
不会处理其信号,因为没有为其或其父类添加处理程序到 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 = $signal; $this->signal->handler($this, 'preAction', [$this, 'preAction']); $this->signal->handler($this, 'postAction', [$this, 'postAction']); } public function action() { $this->signal->send($this, 'preAction'); $this->doSomething( __METHOD__ ); $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'
信号,然后Manager
反过来调用对象上的preAction()
方法 -
调用对象上的
doSomething()
方法(请注意,doSomething()
方法会向Manager
发送它自己的'example_signal'
) -
向
Manager
发送一个'postAction'
信号,然后Manager
反过来调用对象上的postAction()
方法。
如果存在ExampleAnotherChild
类或其父类的基于类的处理器,那些也会被执行。这意味着我们可以设置应用于类的总体组合处理器,以及与特定对象相关的处理器。
高级用法
处理器位置组
默认情况下,所有Handler
对象都将附加到Manager
堆栈,并按添加的顺序进行处理。有时您可能需要将Handler
以不同的顺序处理;例如,在所有其他处理器之前或之后。如果是这样,您可以在将Handler
添加到Manager
时传递一个$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."; return "First closure"; }; $signal->handler('Vendor\Package\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."; return "Second closure"; }; $signal->handler('Vendor\Package\ExampleChild', 'example_signal', $closure, 1000);
添加到位置的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
:原始发送的信号。 -
$value
:处理器回调返回的值。
如果您只需要最后一个结果,您可以在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 $results = $this->signal->send($this, 'mock_signal'); // Or you can get via // $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 [ // first handler, with a closure [ 'Vendor\Package\Example', 'mock_signal', function() { return 'foo'; }, ], // second handler, with a static callback [ 'Vendor\Package\Example', 'mock_signal', ['Vendor\Package\SomeClass', 'someMethod'], ], // third handler, with a closure and position [ '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的原始建议,Galactic Void将其重新提出来,以及Matthew Weier O'Phinney。