我正在尝试使用CakePHP v2.1+中的事件系统
它看起来相当强大,但文档有些模糊。触发事件似乎很直接,但我不知道如何注册相应的侦听器来侦听事件。相关部分在这里,它提供了以下示例代码:
App::uses('CakeEventListener', 'Event');
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
'Model.Order.afterPlace' => 'updateBuyStatistic',
);
}
public function updateBuyStatistic($event) {
// Code to update statistics
}
}
// Attach the UserStatistic object to the Order's event manager
$statistics = new UserStatistic();
$this->Order->getEventManager()->attach($statistics);
但它没有说明这个代码应该驻留在哪里。在特定控制器内?在应用程序控制器内部?
如果它是相关的,监听器将是我正在编写的插件的一部分。
更新:这听起来像是一种流行的方法,将侦听器注册代码放在插件的bootstrap.php文件中。然而,我不知道如何从那里调用getEventManager((,因为应用程序的控制器类等不可用。
更新2:我还被告知听众可以住在模特里面。
更新3:终于有了牵引力!以下代码将在MyPlugin/Config/bootstrap.php 中成功记录事件
App::uses('CakeEventManager', 'Event');
App::uses('CakeEventListener', 'Event');
class LegacyWsatListener implements CakeEventListener {
public function implementedEvents() {
return array(
'Controller.Attempt.complete' => 'handleLegacyWsat',
);
}
public static function handleLegacyWsat($event) { //method must be static if used by global EventManager
// Code to update statistics
error_log('event from bootstrap');
}
}
CakeEventManager::instance()->attach(array('LegacyWsatListener', 'handleLegacyWsat'), 'Controller.Attempt.complete');
我不知道为什么,但当我试图将两个App::uses()
组合成一行时,我不会出错。
事件
事件是与字符串相关联的回调。对象(如Model(将使用字符串触发事件,即使没有任何对象在侦听该事件。
CakePHP是为Models之类的东西预先构建的内部事件。您可以将事件侦听器附加到模型并响应Model.beforeSave
事件。
EventManager
Cake中的每个模型都有自己的EventManager,另外还有一个gobal singleton EventManager。这些并不都是EventManager的同一个实例,它们的工作方式略有不同。
当一个模型触发一个事件时,它会使用它所拥有的EventManager引用来触发。这意味着您可以将事件侦听器附加到特定的Model。优点是侦听器将只接收来自该模型的事件。
全局侦听器是连接到EventManager的singleton实例的侦听器。可以在代码中的任何位置访问。当你在那里连接一个监听器时,无论是谁触发它,它都会为发生的每个事件调用。
当您在应用程序或插件的bootstrap.php
中附加事件侦听器时,您可以使用全局管理器,否则您必须使用ClassRegistry
获得对所需模型的引用。
要使用什么EventManager
如果要处理的事件是针对特定模型的,则将侦听器附加到该模型的EventManager。要获得该模型的参考,您可以调用ClassRegistry::init(...)
。
如果要处理的事件可以在任何地方触发,则将侦听器连接到全局EventManager。
只有你知道应该如何使用你的听众。
听众内部
通常,您将业务逻辑放入模型中。您不需要从事件侦听器访问控制器。模型在Cake中更容易访问和使用。
这里有一个创建CakeEventListener的模板。侦听器负责监视发生的事情,然后将该信息传递给另一个Model。您应该将处理事件的业务逻辑放在Models中。
<?php
App::uses('CakeEventListener', 'Event');
class MyListener implements CakeEventListener
{
/**
*
* @var Document The model.
*/
protected $Document;
/**
* Constructor
*/
public function __construct()
{
// get a reference to a Model that we'll use
$this->Document = ClassRegistry::init('Agg.Document');
}
/**
* Register the handlers.
*
* @see CakeEventListener::implementedEvents()
*/
public function implementedEvents()
{
return array(
'Model.User.afterSave'=>'UserChanged'
);
}
/**
* Use the Event to dispatch the work to a Model.
*
* @param CakeEvent $event
* The event object and data.
*/
public function UserChanged(CakeEvent $event)
{
$data = $event->data;
$subject = $event->subject();
$this->Document->SomethingImportantHappened($data,$subject);
}
}
我喜欢做的是将我的所有事件放在Lib
文件夹中。这使得从源代码中的任何位置访问都非常容易。上述代码将进入App/Lib/Event/MyListener.php
。
附加事件监听器
同样,这取决于你需要倾听哪些事件。您必须了解的第一件事是,必须创建一个对象才能激发事件。
例如
当Calendar
控制器显示索引时,Document
模型不可能激发Model.beforeSave
事件,因为日历控制器从不使用Document
模型。您是否需要在bootstrap.php
中为Document
添加一个侦听器来捕获它何时保存?否,如果Document
模型仅从Documents
控制器使用,那么您只需要在那里附加侦听器。
另一方面,Auth
组件几乎每次都使用User
模型。如果要处理正在删除的User
。您可能需要在bootstrap.php
中附加一个事件侦听器,以确保不会偷偷删除。
在上面的例子中,我们可以像这样直接连接到User
模型
App::uses('MyListener','Lib');
$user = ClassRegistry::init('App.User');
$user->getEventManager()->attach(new MyListener());
此行将导入侦听器类。
App::uses('MyListener','Lib');
此行将获得用户模型的一个实例。
$user = ClassRegistry::init('App.User');
这一行创建了一个监听器,并将其附加到User模型。
$user->getEventManager()->attach(new MyListener());
如果User
模型在许多不同的地方使用。您可能必须在bootstrap.php
中执行此操作,但前提是它仅由一个控制器使用。您可以将该代码放在beforeFilter
中或PHP文件的顶部。
Global EventManager怎么样
假设我们需要倾听一般事件。就像任何东西都被保存一样。我们希望连接到全局EventManager。它会变成这样,并被放置在bootstrap.php
中。
App::uses('MyListener','Lib');
CakeEventManager::instance()->attach(new MyListener());
如果您想在插件的bootstrap.php文件中附加一个事件侦听器,那么使用答案中发布的提示应该可以正常工作。这是我的代码(工作正常(:
MyPlugin/Config/bootstrap.hp:
App::uses('CakeEventManager', 'Event');
App::uses('MyEventListener', 'MyPlugin.Lib/Event');
CakeEventManager::instance()->attach(new MyEventListener());
MyPlugin/Lib/Event/MyEventListener.hp:
App::uses('CakeEventListener', 'Event');
class MyEventListener implements CakeEventListener {
...
}
只有在加载插件时,才会注册与MyPlugin相关的事件侦听器。如果我不想使用该插件,则不会附加事件侦听器。当你想使用插件在应用程序的各个地方添加一些功能时,我认为这是一个干净的解决方案。
代码所在的位置并不重要。只需确保它正在执行,并且您的事件已正确注册&附件。
我们使用的是一个单独的文件,所有事件都附加在该文件中,并从bootstrap.php中包含,这确保了所有事件都可以从应用程序中的所有位置获得。
当你调度一个事件时,就像从控制器动作中调度一样,神奇的事情就会发生。
$event = new CakeEvent('Model.Order.afterPlace', $this, array('some'=>'data') ));
$this->getEventManager()->dispatch($event);
但是,您可以从任何可以到达EventManager的地方调度事件(默认情况下在Models、Controller和Views中(