Getting to grips with CakePHP’s events system
CakePHP seems to get a slightly unfavourable reputation when compared to the likes of Symfony or Zend Framework due to its lack of namespaces and not playing nicely with Composer out of the box. However, that will change in the forthcoming version 3; and CakePHP 2 still remains a pretty easy PHP framework to work with and quickly build web applications with.
A design pattern that is pretty common in MVC applications is the Observer pattern, colloquially known as event handlers. From the Wikipedia entry, it’s defined as:
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
So plainly put: when something changes in your application, you can have code somewhere else that does something too. This makes for a better separation of concerns and more modular code that’s easier to maintain.
The events system in CakePHP
CakePHP comes with a built-in events system but it’s poorly documentated, and not the most straightforward of things based on the number of questions on Stack Overflow surrounding it. CakePHP’s implementation follows the traditional Observer pattern set-up pretty closely:
- There are subjects, which may be a model or a controller
- Subjects raise events
- An observer (or listener) is “attached” to subjects and “listens” for events to be raised
So let’s think of a scenario…
The scenario
A website that accepts user registrations. When a user registers an account is created for them, but is initially inactive. A user has to activate their account by clicking a link in an email.
One approach would be just to put the code that sends the activation email in the User model itself:
<?php
class User extends AppModel {
public function afterSave($created, $options = array()) {
if ($created) {
$email = new CakeEmail();
$email->to($this->data[$this->alias]['email']);
$email->from(array(
'noreply@example.com' => 'Your Site'
));
$email->subject('Activate your account');
$email->format('text');
$email->template('new_user');
$email->viewVars(array(
'user' => $this->data[$this->alias]
));
$email->send();
}
}
}
But this is mixing concerns. We don’t want the code that sends the activation email in our User model. The model should just deal with retrieving, saving, and deleting User records.
So what can we do? We can implement the Observer pattern.
Raising events
First we can remove the email sending code from our afterSave() callback method, and instead raise an event:
<?php
App::uses('CakeEvent', 'Event');
class User extends AppModel {
public function afterSave($created, $options = array()) {
if ($created) {
$event = new CakeEvent('Model.User.created', $this, array(
'id' => $this->id,
'data' => $this->data[$this->alias]
));
$this->getEventManager()->dispatch($event);
}
}
}
As you can see, our afterSave() method is now much leaner.
Also note the App::uses() statement added to the top of the file, to make sure the CakeEvent class is imported. We’re creating an instance of the CakeEvent event class, passing it an event name (“Model.User.created”), a subject ($this), and some data associated with this event. We want to pass the newly-created record’s ID, and the data of this record.
With event names in CakePHP, it’s recommended to use pseudo name-spacing. In the above example, the first portion is the tier (Model), the second portion is the object within that tier (User), and the third portion is a description name of the event (created). So we know from the event name that it’s when a new user record is created.
Creating a listener
Now we have events being raised, we need code to listen for them.
The first step is to create code to do something when an event is raised. This is where CakePHP’s documentation starts getting hazy. It provides sample code, but it doesn’t tell you where to actually put it. I personally created an Event directory at the same level as Config, Controller, Model etc. I then name my class after what it’s doing. For this handler, I’m going to call it UserListener and save it as UserListener.php.
Event listeners in CakePHP implement the CakeEventListener interface, and as a result need to implement one method called implementedEvents(). The skeleton code for the listener class then looks like this:
<?php
App::uses('CakeEventListener', 'Event');
class UserListener implements CakeEventListener {
public function implementedEvents() {
// TODO
}
}
The implementedEvents() method expects an associative array mapping event names to methods that should handle such events. So let’s flesh that out with the one event we’re raising:
public function implementedEvents() {
return array(
'Model.User.created' => 'sendActivationEmail'
);
}
Simples.
So now, we need to actually create that sendActivationEmail() method we’ve specified. This is where we would put the code to be ran when a user is created.
public function sendActivationEmail(CakeEvent $event) {
// TODO
}
The method is passed one argument: an instance of CakeEvent. In fact, this would be the CakeEvent instance you raise in your User model. We set some data there (an ID and the current record’s data), and that data is now going to available to us in the instance passed to our listener method.
So now we know what we’re getting, let’s flesh our listener method out some more with that email sending code:
public function sendActivationEmail(CakeEvent $event) {
$this->User = ClassRegistry::init('User');
$activationKey = Security::generateAuthKey();
$this->User->id = $event->data['id'];
$this->User->set(array(
'active' => false,
'activation_key' => $activationKey
));
$this->User->save();
$email = new CakeEmail();
$email->from(array(
'noreply@example.com' => 'Your Site'
));
$email->to($event->data['user']['email']);
$email->subject('Activate your account');
$email->template('new_user');
$email->viewVars(array(
'firstName' => $event->data['user']['first_name'],
'activationKey' => $activationKey
));
$email->emailFormat('text');
$email->send();
}
The code above is doing the following:
- Creating an instance of the
Usermodel, as we don’t initially have it available in our listener class - Generating an activation key for the user
- Setting the activation key for the user in the database, whose ID we get from the event raised
- Sending the activation email, with our generated activation key
And that’s all there is to our listener class.
Because we’re using the CakeEmail and Security classes in CakePHP, it’s a good idea to make sure they’re loaded. At the top of the file, add these two lines:
App::uses('CakeEmail', 'Network/Email');
App::uses('Security', 'Utility');
Attaching the listener
We now have two out of three components in our Observer pattern set up: events are being raised, and we have code to act on raised events; we just need to hook the two together now. This is where CakePHP’s documentation just leaves you completely on your own.
One approach is to do this in the app/Config/bootstrap.php file. We need to create an instance of our event listener class and attach it to the User model using its event manager.
The code is simple. At the bottom of your bootstrap.php add the following code:
App::uses('ClassRegistry', 'Utility');
App::uses('UserListener', 'Event');
$user = ClassRegistry::init('User');
$user->getEventManager()->attach(new UserListener());
As you can see, we’re using CakePHP’s ClassRegistry utility class to load the Usermodel; and then using the User model’s event manager to attach our UserListenerclass. So now when the User model fires an event, our UserListener class (and any other listener classes attached to it) will be listening for it. Neat!
Conclusion
Hopefully you can see the merits of the Observer pattern. This is just one example; there are many other use cases where this pattern would be appropriate. Hopefully this blog post will demystify CakePHP’s implementation of this design pattern and you can find areas in your own applications where you can apply it yourself.
If you do use CakePHP’s events system in your own applications, then I’d love to see your implementations and the problems you solved using it.
Getting to grips with CakePHP’s events system的更多相关文章
- cakephp 的事件系统(Getting to grips with CakePHP’s events system), 基于观察者模式
This article was written about CakePHP 2.x and has been untested with CakePHP 3.x CakePHP seems to g ...
- Java I/O and NIO [reproduced]
Java I/O and NIO.2---Five ways to maximize Java NIO and NIO.2---Build more responsive Java applicati ...
- Android源码——Logger日志系统
Android的Logger日志系统是基于内核中的Logger日志驱动程序实现的. 日志保存在内核空间中 缓冲区保存日志 分类方法:日志的类型 + 日志的输出量 日志类型: main ...
- Microsoft Win32 to Microsoft .NET Framework API Map
Microsoft Win32 to Microsoft .NET Framework API Map .NET Development (General) Technical Articles ...
- Android开发艺术探索笔记——View(二)
Android开发艺术探索笔记--View(二) View的事件分发机制 学习资料: 1.Understanding Android Input Touch Events System Framewo ...
- Revit二次开发示例:EventsMonitor
在该示例中,插件在Revit启动时弹出事件监控选择界面,供用户设置,也可在添加的Ribbon界面完成设置.当Revit进行相应操作时,弹出窗体会记录事件时间和名称. #region Namespace ...
- Five ways to maximize Java NIO and NIO.2--reference
Java NIO -- the New Input/Output API package-- was introduced with J2SE 1.4 in 2002. Java NIO's purp ...
- Docker 1.13 管理命令
1.12 CLI 的问题 Docker1.12 命令行接口(CLI)有40多个顶级命令,这些命令存在以下问题: 没有归类组织,这让docker 新手很难学习: 有些命令没有提供足够的上下文环境,以至于 ...
- Oracle Applications DBA 基础(一)
1.引子 2014年9月13日 20:33 <oracle Applications DBA 基础>介绍Oracle Applications R12的系统架构, 数据库后台及应用系统的基 ...
随机推荐
- angularjs 微信授权登录 微信支付
最近做一个项目,用angular 一个单页应用,打算打包成 跨平台移动App 以及在微信里面使用.给大家一个案例 首先,熟悉一下微信授权部分的源代码,如下所示: javascript 前端代码: va ...
- 【机器学习】k-近邻算法以及算法实例
机器学习中常常要用到分类算法,在诸多的分类算法中有一种算法名为k-近邻算法,也称为kNN算法. 一.kNN算法的工作原理 二.适用情况 三.算法实例及讲解 ---1.收集数据 ---2.准备数据 -- ...
- 寻找与网页内容相关的图片(三)网易新闻与qq空间的做法
寻找与网页相关的图片现在看来无非有两种方式,第一种是网页自己指定,第二种是通过算法推断. 对于网站的内容提供者来说,他自己知道相关的图片在哪,正如前文所述可以在HTML的头部加上META标签,也可以像 ...
- H264(ES)如何打包成H264(PES)
http://blog.csdn.net/u013898698/article/details/64919036 http://blog.csdn.net/cabbage2008/article/de ...
- 【转载】TextView源码解析
原文地址:https://github.com/7heaven/AndroidSdkSourceAnalysis/blob/master/article/textview%E6%BA%90%E7%A2 ...
- javascript 的回调函数
既然函数可以像其他数据那样赋值给某个个变量,可以被定义.删除.拷贝,那为什么就不能被当成参数传递给其他函数呢? 下面的示例中,我们定义了一个以两个函数为参数的函数.该函数会分别执行这两个参数函数,并返 ...
- UVa 10562 Undraw the Trees 看图写树
转载请注明: 仰望高端玩家的小清新 http://www.cnblogs.com/luruiyuan/ 题目大意: 题目传送门:UVa 10562Undraw the Trees 给定字符拼成的树,将 ...
- ZOJ 2967 Colorful Rainbows
暴力. 先删掉一些边,平行的线只保留$b$最大的.然后暴力,每次放入第$i$条边,和还没有被完全覆盖的边都算一遍,更新一下. #pragma comment(linker, "/STACK: ...
- POJ3468 A Simple Problem with Interger [树状数组,差分]
题目传送门 A Simple Problem with Integers Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 1 ...
- redis的运行机制
从以前总结的redis一些基本性能中,可知redis是非关系型数据库(nosql):这一类的数据类型有以下特点: 非关系型的(sql语句对它不起作用,不需要建表存数据,它是直接存储),分布式(主从复制 ...