mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 06:15:19 +08:00
Fixes #1025: Implemented support for class-level events.
This commit is contained in:
@ -358,13 +358,13 @@ class Component extends Object
|
||||
public function hasEventHandlers($name)
|
||||
{
|
||||
$this->ensureBehaviors();
|
||||
return !empty($this->_events[$name]);
|
||||
return !empty($this->_events[$name]) || Event::hasHandlers($this, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an event handler to an event.
|
||||
*
|
||||
* An event handler must be a valid PHP callback. The followings are
|
||||
* The event handler must be a valid PHP callback. The followings are
|
||||
* some examples:
|
||||
*
|
||||
* ~~~
|
||||
@ -374,7 +374,7 @@ class Component extends Object
|
||||
* 'handleClick' // global function handleClick()
|
||||
* ~~~
|
||||
*
|
||||
* An event handler must be defined with the following signature,
|
||||
* The event handler must be defined with the following signature,
|
||||
*
|
||||
* ~~~
|
||||
* function ($event)
|
||||
@ -455,6 +455,7 @@ class Component extends Object
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::trigger($this, $name, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,4 +45,136 @@ class Event extends Object
|
||||
* Note that this varies according to which event handler is currently executing.
|
||||
*/
|
||||
public $data;
|
||||
|
||||
private static $_events = [];
|
||||
|
||||
/**
|
||||
* Attaches an event handler to a class-level event.
|
||||
*
|
||||
* When a class-level event is triggered, event handlers attached
|
||||
* to that class and all parent classes will be invoked.
|
||||
*
|
||||
* For example, the following code attaches an event handler to `ActiveRecord`'s
|
||||
* `afterInsert` event:
|
||||
*
|
||||
* ~~~
|
||||
* Event::on([ActiveRecord::className, ActiveRecord::EVENT_AFTER_INSERT], function ($event) {
|
||||
* Yii::trace(get_class($event->sender) . ' is inserted.');
|
||||
* });
|
||||
* ~~~
|
||||
*
|
||||
* The handler will be invoked for EVERY successful ActiveRecord insertion.
|
||||
*
|
||||
* For more details about how to declare an event handler, please refer to [[Component::on()]].
|
||||
*
|
||||
* @param string $class the fully qualified class name to which the event handler needs to attach
|
||||
* @param string $name the event name
|
||||
* @param callback $handler the event handler
|
||||
* @param mixed $data the data to be passed to the event handler when the event is triggered.
|
||||
* When the event handler is invoked, this data can be accessed via [[Event::data]].
|
||||
* @see off()
|
||||
*/
|
||||
public static function on($class, $name, $handler, $data = null)
|
||||
{
|
||||
self::$_events[$name][ltrim($class, '\\')][] = [$handler, $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches an event handler from a class-level event.
|
||||
*
|
||||
* This method is the opposite of [[on()]].
|
||||
*
|
||||
* @param string $class the fully qualified class name from which the event handler needs to be detached
|
||||
* @param string $name the event name
|
||||
* @param callback $handler the event handler to be removed.
|
||||
* If it is null, all handlers attached to the named event will be removed.
|
||||
* @return boolean if a handler is found and detached
|
||||
* @see on()
|
||||
*/
|
||||
public static function off($class, $name, $handler = null)
|
||||
{
|
||||
$class = ltrim($class, '\\');
|
||||
if (empty(self::$_events[$name][$class])) {
|
||||
return false;
|
||||
}
|
||||
if ($handler === null) {
|
||||
unset(self::$_events[$name][$class]);
|
||||
return true;
|
||||
} else {
|
||||
$removed = false;
|
||||
foreach (self::$_events[$name][$class] as $i => $event) {
|
||||
if ($event[0] === $handler) {
|
||||
unset(self::$_events[$name][$class][$i]);
|
||||
$removed = true;
|
||||
}
|
||||
}
|
||||
if ($removed) {
|
||||
self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
|
||||
}
|
||||
return $removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value indicating whether there is any handler attached to the specified class-level event.
|
||||
* Note that this method will also check all parent classes to see if there is any handler attached
|
||||
* to the named event.
|
||||
* @param string|object $class the object or the fully qualified class name specifying the class-level event
|
||||
* @param string $name the event name
|
||||
* @return boolean whether there is any handler attached to the event.
|
||||
*/
|
||||
public static function hasHandlers($class, $name)
|
||||
{
|
||||
if (empty(self::$_events[$name])) {
|
||||
return false;
|
||||
}
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
} else {
|
||||
$class = ltrim($class, '\\');
|
||||
}
|
||||
do {
|
||||
if (!empty(self::$_events[$name][$class])) {
|
||||
return true;
|
||||
}
|
||||
} while (($class = get_parent_class($class)) !== false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a class-level event.
|
||||
* This method will cause invocation of event handlers that are attached to the named event
|
||||
* for the specified class and all its parent classes.
|
||||
* @param string|object $class the object or the fully qualified class name specifying the class-level event
|
||||
* @param string $name the event name
|
||||
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
|
||||
*/
|
||||
public static function trigger($class, $name, $event = null)
|
||||
{
|
||||
if (empty(self::$_events[$name])) {
|
||||
return;
|
||||
}
|
||||
if ($event === null) {
|
||||
$event = new self;
|
||||
}
|
||||
$event->handled = false;
|
||||
$event->name = $name;
|
||||
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
} else {
|
||||
$class = ltrim($class, '\\');
|
||||
}
|
||||
do {
|
||||
if (!empty(self::$_events[$name][$class])) {
|
||||
foreach (self::$_events[$name][$class] as $handler) {
|
||||
$event->data = $handler[1];
|
||||
call_user_func($handler[0], $event);
|
||||
if ($event instanceof Event && $event->handled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (($class = get_parent_class($class)) !== false);
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +302,6 @@ class ComponentTest extends TestCase
|
||||
$component->detachBehaviors();
|
||||
$this->assertNull($component->getBehavior('a'));
|
||||
$this->assertNull($component->getBehavior('b'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
93
tests/unit/framework/base/EventTest.php
Normal file
93
tests/unit/framework/base/EventTest.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace yiiunit\framework\base;
|
||||
|
||||
use yii\base\Component;
|
||||
use yii\base\Event;
|
||||
use yiiunit\TestCase;
|
||||
|
||||
/**
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class EventTest extends TestCase
|
||||
{
|
||||
public $counter;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->counter = 0;
|
||||
Event::off(ActiveRecord::className(), 'save');
|
||||
Event::off(Post::className(), 'save');
|
||||
Event::off(User::className(), 'save');
|
||||
}
|
||||
|
||||
public function testOn()
|
||||
{
|
||||
Event::on(Post::className(), 'save', function ($event) {
|
||||
$this->counter += 1;
|
||||
});
|
||||
Event::on(ActiveRecord::className(), 'save', function ($event) {
|
||||
$this->counter += 3;
|
||||
});
|
||||
$this->assertEquals(0, $this->counter);
|
||||
$post = new Post;
|
||||
$post->save();
|
||||
$this->assertEquals(4, $this->counter);
|
||||
$user = new User;
|
||||
$user->save();
|
||||
$this->assertEquals(7, $this->counter);
|
||||
}
|
||||
|
||||
public function testOff()
|
||||
{
|
||||
$handler = function ($event) {
|
||||
$this->counter ++;
|
||||
};
|
||||
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
|
||||
Event::on(Post::className(), 'save', $handler);
|
||||
$this->assertTrue(Event::hasHandlers(Post::className(), 'save'));
|
||||
Event::off(Post::className(), 'save', $handler);
|
||||
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
|
||||
}
|
||||
|
||||
public function testHasHandlers()
|
||||
{
|
||||
$this->assertFalse(Event::hasHandlers(Post::className(), 'save'));
|
||||
$this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save'));
|
||||
Event::on(Post::className(), 'save', function ($event) {
|
||||
$this->counter += 1;
|
||||
});
|
||||
$this->assertTrue(Event::hasHandlers(Post::className(), 'save'));
|
||||
$this->assertFalse(Event::hasHandlers(ActiveRecord::className(), 'save'));
|
||||
|
||||
$this->assertFalse(Event::hasHandlers(User::className(), 'save'));
|
||||
Event::on(ActiveRecord::className(), 'save', function ($event) {
|
||||
$this->counter += 1;
|
||||
});
|
||||
$this->assertTrue(Event::hasHandlers(User::className(), 'save'));
|
||||
$this->assertTrue(Event::hasHandlers(ActiveRecord::className(), 'save'));
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveRecord extends Component
|
||||
{
|
||||
public function save()
|
||||
{
|
||||
$this->trigger('save');
|
||||
}
|
||||
}
|
||||
|
||||
class Post extends ActiveRecord
|
||||
{
|
||||
}
|
||||
|
||||
class User extends ActiveRecord
|
||||
{
|
||||
|
||||
}
|
Reference in New Issue
Block a user