Yes, I know I work for Zend and that means that I should automatically be familiar with everything the company does. Especially when it comes to Zend Framework 2. But I have to confess that while I’m most definitely watching it, I have not been able to work with it in any meaningful sense.
Until today. I got to play with the Event Manager. I did like the plugin functionality in ZF1, but it required some pretty static coding. In some cases, like the front controller plugins, it makes more sense (though this way seems more desirable). Of course there are frameworks out there that are more event based as a whole, but what if you’re more familiar with Zend Framework? I am, and so it makes sense that I would use the event manager in ZF2. It’s a ZF1 application, but since (it seems) the event manager is self-contained (and the autoloader works with both ZF1 and ZF2) you can simply paste it into your include_path and BOOM, you have an event manager.
So, I had a model (the software kind) layer where all of the models were based off of an abstract class. The abstract class had a save() method that I wanted to provide various hooks for pre and post save and, more importantly, pre and post validation. Why validation? Because I want to stored a hashed password in the database, but I want to validate on the plaintext. I could have created a bunch of hooks, but that really is annoying. I could have also overridden the save() method, but that meant adding a fair amount of logic to that method and I’m becoming less and less a fan of overriding logic because it’s insufficient at higher levels of abstraction. So, instead, I implemented the ZF2 event manager in a ZF1 application.
The model looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| abstract class ModelAbstract
{
const EVENT_PRESAVE = 'modelabstract-pre-save';
const EVENT_PREVALIDATE = 'modelabstract-pre-validate';
const EVENT_POSTVALIDATE = 'modelabstract-post-validate';
const EVENT_POSTSAVE = 'modelabstract-post-save';
/**
* @var EventManager
*/
protected $eventManager;
public final function __construct()
{
$this->eventManager = new EventManager();
$this->init();
}
public function init() {}
public function save()
{
if ($this->tableName === null) {
throw new ModelException('Improperly defined model');
}
$this->eventManager->trigger(self::EVENT_PREVALIDATE, $this);
if (!$this->getForm()->isValid($this->data)) {
return false;
}
$this->eventManager->trigger(self::EVENT_POSTVALIDATE, $this);
$db = \Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('db');
$this->eventManager->trigger(self::EVENT_PRESAVE, $this);
// save the data (redacted due to boredom)
$this->eventManager->trigger(self::EVENT_POSTSAVE, $this);
return $ret;
}
} |
Then in my Account model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| class Application_Model_Account extends ModelAbstract
{
protected $tmpPassword;
public function init()
{
// Encrypt the password after validation if the password has changed
$this->eventManager->attach(
self::EVENT_POSTVALIDATE,
function($event) {
if ($event->getTarget()->passwordChanged()) {
$event->getTarget()->encryptPassword();
}
}
);
}
public function passwordChanged() // really annoying. Thanks PHP 5.3
{
return $this->tmpPassword == null;
}
public function encryptPassword()
{
if ($this->passwordChanged()) {
$util = new Util();
$this->password = $util->encryptPassword($password);
$this->tmpPassword = null;
}
}
} |
If the password is changed it sets both $this->password and $this->tmpPassword. The hashing only occurs if the password has been set and there’s a value in tmpPassword. (I guess I could have just used a boolean somewhere too). So now, every time I save the account model the post validate event will be fired and the password will be encrypted. (The comment on PHP 5.3 is because you cannot use $this in a closure in PHP 5.3. That functionality will be there in 5.4.)
I believe that the method I used here allows for events to be fired for specific object instances, because each model has its own event manager. But the event manager will also check for global events as well. So, technically, the code that I have that does validation could be registered as a global observer to make sure that all models are properly validated prior to the save as well as having instance-specific (such as password changes) events attached to the same event name.
Pretty cool.
… waiting for Matthew to email me with corrections…
[UPDATE]
For those who have complained that I didn’t use some additional kind of methodology or best practice know that the other five billion lines of code that had nothing whatsoever to do with the event manager has been removed… or read my response down below on “Methodology Jackassery”.