This article was written about CakePHP 2.x and has been untested with CakePHP 3.x

CakePHP seems to get a slightly unfavourable reputation when compared to the likes of Symfony orZend 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 ConfigControllerModel etc. I then name my class after what it’s doing. For this handler, I’m going to call itUserListener and save it as UserListener.php.

Event listeners in CakePHP implement the CakeEventListener interface, and as a result need to implement one method calledimplementedEvents(). 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 yourUser 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 User model, 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 User model; and then using the User model’s event manager to attach our UserListener class. 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.

cakephp 的事件系统(Getting to grips with CakePHP’s events system), 基于观察者模式的更多相关文章

  1. Getting to grips with CakePHP’s events system

    CakePHP seems to get a slightly unfavourable reputation when compared to the likes of Symfony or Zen ...

  2. 主流PHP框架间的比较(Zend Framework,CakePHP,CodeIgniter,Symfony,ThinkPHP,FleaPHP)

    Zend Framework 优点: Zend Framework大量应用了PHP5中面向对象的新特征:接口.异常.抽象类.SPL等等.这些东西的应用让Zend Framework具有高度的模块化和灵 ...

  3. CakePHP不支持path/to路径,前后台无法方法

    本来想把前后台分离,可是阅读了cakephp的说明,才发现.cakephp根本就不支持path/to路径. cakephp官网给出的 管理员分离方式:http://book.cakephp.org/2 ...

  4. 使用GitLab CI + Capistrano部署CakePHP应用程序

    使用GitLab CI + Capistrano部署CakePHP应用程序 摘要:本文描述了如使用GitLab CI + Capistrano部署CakePHP应用程序. 目录 1. 问题2. 解决方 ...

  5. 从CakePHP 1.3升级到2.5

    从CakePHP 1.3升级到2.5 摘要:最近把一个CakePHP 1.3的项目升级到了2.x,当然就用最新的版本2.5.3了,结果基本满意.本文记录了升级的过程,包括使用的工具,遇到的问题和相应的 ...

  6. InnoDB与UUID

    CakePHP本身有一个uuid实现,所以一直以来,我都在尝试使用uuid做主键的可能性.虽然MySQL是我最常用的数据库,但是和 auto_increment_int主键相比,我对uuid主键更有好 ...

  7. PHP代码审计(初级篇)

    一.常见的PHP框架 1.zendframwork: (ZF)是Zend公司推出的一套PHP开发框架 功能非常的强大,是一个重量级的框架,ZF 用 100%面向对象编码实现. ZF 的组件结构独一无二 ...

  8. Unity3d项目入门之虚拟摇杆

    Unity本身不提供摇杆的组件,开发者可以使用牛逼的EasyTouch插件或者应用NGUI实现相关的需求,下面本文通过Unity自身的UGUI属性,实现虚拟摇杆的功能. 主参考 <Unity:使 ...

  9. JavaScript之DOM等级概述

    这两日对DOM等级的理解不是太通透,就进Mozilla官网去看了一下,然后进行了首次的对技术文档的翻译工作,虽然官网也有中文解释,但我想,自己翻译出来时,已经有了原汁原味的理解了吧,这边是做此次翻译的 ...

随机推荐

  1. java 图形界面 mvc模式控制

    使用模型-视图-控件结构来开发GUI程序. 下面的程序演示了MVC模式开发的java程序. 其中CircleModel为模型,包含了圆的半径,是否填充,等属性. CircleView为视图,显示这个圆 ...

  2. .NET技术+25台服务器怎样支撑世界第54大网站

    摘要:同时使用Linux和Windows平台产品,大量使用静态的方法和类,Stack Overflow是个重度性能控.同时,取代横向扩展,他们坚持着纵向扩展思路,因为“硬件永远比程序员便宜”. Sta ...

  3. Linux 格式化分区 报错Could not stat --- No such file or directory 和 partprobe 命令

    分区的过程正常: [root@db1 /]# fdisk -l   Disk /dev/sda: 21.4 GB, 21474836480 bytes 255 heads, 63 sectors/tr ...

  4. SELECT TOP 1 * FROM是什么意思

    SELECT TOP 1 * FROM的含义: 1.select为命令动词,含义为执行数据查询操作: 2.top 1子句含义为查询结果只显示首条记录: 3.*子句表示查询结果包括数据源中的所有字段: ...

  5. 第四十三节,文件、文件夹、压缩包、处理模块shutil

    文件.文件夹.压缩包.处理模块shutil 文件处理 copyfileobj()模块函数 功能:将a文件的内容,复制到b文件中[有参] 使用方法:模块名称.copyfileobj(poen(" ...

  6. 第十三节,基本数据类型,数字int字符串str

    基本数据类型 数字 int 字符串 str 布尔值 bool 列表 list 元组 tuple 字典 dict 数据类型关系图 查看一个对象的类 如:如查看对象变量a是什么类          用到函 ...

  7. git基本命令--tag, alias,

    git tag: 列出标签 在 Git 中列出已有的标签是非常简单直观的. 只需要输入 git tag: $ git tag v0. v1. 这个命令以字母顺序列出标签:但是它们出现的顺序并不重要. ...

  8. AngularJS展示数据的ng-bind指令和{{}} 区别

    在AngularJS中显示模型中的数据有两种方式: 一种是使用花括号插值的方式: 1 <p>{{text}}</p> 另一种是使用基于属性的指令,叫做ng-bind: 1 &l ...

  9. shell:crontab

    crontab */1 * * * * (cd /home/q/system/project; /usr/bin/lockf -t 0 /tmp/discuz_bbs_audit.lock /usr/ ...

  10. .net文件上传,客户端用jquery file upload

    <%@ WebHandler Language="C#" Class="Handler" %> using System; using System ...