Zend Framework 2 Event Manager

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”.

7 comments
Tomas Dermisek
Tomas Dermisek

Hi, now my comment is not Event Manager specific, but I think the demonstrated class structure violates SOLID principles - the Abstract class takes too many responsibilities (persistence, validation, value filtering, etc) and then every subclass just adds some more.

Furthermore I think it would be quite difficult to unit test such complex construct.

How would you handle contextual validation? Eg. one user type can have 10 digits password and another user type 20 digits?

Thanks.

Oscar
Oscar

This is awesome to hear. I've been using Symfony's Event component with a ZF1 app that I'm working on, but if I can use ZF2's Event Manager even better.

Kees Schepers
Kees Schepers

Hmm, looks like a pretty cool improvement, at least some better as the current release. As a used Doctrine/ZF user I still prefer Doctrine with events based on annotations. And that you don't need to extends some Doctrine abstract class.

Happy ZF'ing!

Kevin Schroeder
Kevin Schroeder

Let me start by going backwards. First, I presume that the example of a 10 digit password for one user and a 20 for another is a bad example. A better one would be someone from the US having to select from a group of states whereas someone from Canada would have to select from a group of provinces. In that scenario it is up to the validator chain to figure out how to handle it. The abstract model only cares if the data is valid or not, the model instance itself is responsible for making sure that the validator definitions are correct.

Unit testing has not been difficult at all. The structure is not really that complex and all of the pieces are individually testable.

I don't think that this violates single responsibility. It doesn't do persistence, it passes that off to the DB adapter. It doesn't do validation, the form validates as well as does any filtering. BUT, the purpose of the abstract model class is to make sure that the data it is representing is workable for the rest of the application. Each model requires persistence and validation/filtering. There are no exceptions (pun intended :-) ). Now, what I can do is create a separate class that handles saving and validating the model and so breaks some of the dependencies in the model, but why? It then has the same dependencies as the abstract model class. The abstract class takes on the minimum functionality that will be required across all models.

As a bit of an off-topic rant (Tomas, this is NOT directed at you, but something that bugs me in general) I think we need to be careful how strongly we adhere to individual programming concepts. As with any complex discipline, computer science will have multiple conflicting views. When a discipline/theory claims to have no contradicting data/methodologies that is a strong sign that its data/methodology is flawed (one of the reasons why I am skeptical of several theories which are "apparently" slam-dunks for which the data is "incontrovertible"). There are several thought leaders who I could quote on this, both in general and in specific to software development. Every methodology has its limits. MVC will contradict SOLID will contradict DRY will contradict TDD at some point. Not in large parts but individual concepts will have pieces that contradict other concepts and problems can occur when someone subscribes too rigidly to a given principle in ALL cases. A good developer will try to get as close as they can, but they must be open to imperfection in their applications. I have seen soooo many applications crumble under the weight of perfection.

Sorry if this sounds like I'm laying into you, Tomas. I'm not. This is just something that runs through my head every time I'm told that I'm not following some XYZ principle to the letter. Perhaps I'm being defensive. I don't write code so I get a gold star on my architecture. I write it because I have a need that needs to be filled. But that said, the opposite can be true where one ignores good principles and the application crumbles under the weight of its imperfection. In my experience that is, more often than not, the problem. But while imperfection destroys an application, so does perfection.

Tomas Dermisek
Tomas Dermisek

Hi, I understand your what you are trying to say and don't take it offensive or anything. I just used those principles trying to easier express what was on my mind, because I learned from experience to adhere to them. This can turn into long abstract debate, so it is hard to add something, but thanks for your answer - and for your contribution to the Zend or PHP community. I really appreciate it :)

Wil Moore III
Wil Moore III

Kevin,

I get what you are saying in your rant; however, the "perfection" angle seems confusing to me.

I think what you might be getting at is that sometimes developers have a tendency to gravitate toward a principle and cargo cult without thinking about its benefits as well as potential limitations in the context of their given problem. This practice can lead to danger; however, I'd also caution those witnessing this to not "Throw out the baby with the bath water".

Kevin Schroeder
Kevin Schroeder

Of course not. Many of the programming methodologies we have are good, but not always appropriate. And sometimes people just say silly things like "You're not doing X. Clearly you're not l33t." Or the omit the second part, but insinuate it in the first part. Too often (very) smart people will apply too much theory to a situation and then complain about the situation.

My first experience with this was several years ago when I had to take over a small project from a Java developer that was supposed to watch for spammers on a large email system. I did some performance tests ( I was a sysadmin at the time) and it failed miserably. The Java dev didn't have time to fix it so I took it on.

It took a little bit of time for to get through it, but it was a great example of great code. It really was. But it failed the business requirements. I re-wrote it in a way that would make a Java developer puke and increased its performance a hundred-fold.

Some would say "oh that's Java" when what it really was was that the developer didn't stop and ask if the way he was doing it was needed for this circumstance. It wasn't, in this case.

Does that mean that one should throw that methodology out? By no means! I think a lot of PHP devs need to be careful not to swing the pendulum too far. MUST use TDD. MUST use DI. MUST use Scrum. MUST use CI. These are all great things, and likely the way you should be working. But not one of them is the Holy Grail and so the smart devs need to withhold judgment since there are often circumstances that downgrade the MUST's to "should's".

Trackbacks

  1. [...] Schroeder has a new post today sharing some of his experience with the Zend Framework 2 Event Manager in a simple example of pre- and post-validation hooks in a model. I got to play with the Event [...]

  2. [...] Schroeder hat einen neuen Beitrag heute teilen einige seiner Erfahrungen mit dem Zend Framework 2 Event Manager in ein einfaches Beispiel für Pre-und Post-Validierung Haken in einem Modell. Ich habe mit dem [...]

  3. [...] Schroeder has a new post today sharing some of his experience with the Zend Framework 2 Event Manager in a simple example of pre- and post-validation hooks in a model. I got to play with the Event [...]

  4. [...] » Blog Archive » Zend Framework 2 Event Managerhttp://www.eschrade.com/page/zend-framework-2-event-manager [...]

  5. [...] “response”). You can find out more about ZF2’s Event Manager in other posts like this one from Kevin Schroeder or this from Matthew Weier [...]

Post Navigation

Web Analytics