Observer Pattern the CakePHP way

First, I’d like to thank the original author of this article for giving me inspiration, food for thought and a solid code base to further dwell on this idea.
http://bakery.cakephp.org/articles/view/observable-models

So what is the Observer Pattern anyway, and why is it a good thing to implement?

Many resources will tell you that the Observer Pattern is based on the two main concepts: The Subject or the the object being watched and the Observer, the object that is doing the observation. Let’s see if we can take a closer look in more practical terms.
Imagine that we have a User model (The Subject), you can save the User, edit the User, search the User, delete the User. All of these events can be observed and in general we can say that whenever one such event takes place, it means that the User state has changed in some way.

Now, let’s say whenever such state change takes place other parts of our system should perform a certain action, for example when a new User is saved an email should go out.

Of course you’d say that this is pretty easily done in the controller, for example:

if($this->User->save($this->data)) {
  $this->Email->send('saved');
}

We’ve seen this many, many times before. However there is a problem with such implementation. First of all we are adding unnecessary logic to our controller, secondly if we need to perform other actions, besides sending an email, but in fact notify twenty other parts of the system (such as maybe logging some info, moving the User into a guest group, setting up an LDAP account, registering him/her at some remote web site… blah… blah) you’ll see how quickly the code can grow and if the necessary logic isn’t neatly tucked away it will become a serious nightmare to maintain.

Therefore one of the most important benefits of the Observer Pattern is decoupling between various parts of the system.

Let’s continue with this example…

The User object is saved, but now we have an Observer watching/listening for certain events. As soon as it sees that the new User was saved the observer kicks-in and begins to do its job. The great news is that the User object doesn’t care about anything beyond saving the user data, it is the Observer’s responsibility to handle additional tasks as deemed necessary.
Another good bit of news is that the User object can (and probably will) have multiple Observers watching for its every action, each one can then take care of the tasks it was meant to do (i.e. sending out an email, logging, creating account, doing registration, buying flowers). Each one of these tasks can be handled (if desired) by a separate observer.

Again we are ensuring reduced coupling and improving our system’s flexibility and maintainability along the way… and as your application tends to grow, these are very good things.

Now, that we’ve established some ideas about the Observer Pattern, let’s take a look at the CakePHP implementation (again largely based on the article by Chris Lewis).

Our models are going to become Observable by using the Observable Behavior and our Observers will be registered as components of various controllers.

Since the behavior implements all of the callback methods of the model, our Observer will be able to handle any event associated with the model i.e.:

beforeFind();
afterFind();
beforeSave();
afterSave();
beforeDelete();
afterDelete();

The behavior and the component are at the end of article. Please note, that the component code is a skeleton one, i.e. it doesn’t implement any specific functionality, because whatever your observer needs to implement is highly depended on your specific application. However it should give you a solid foundation to build many various observers.
Eventually I will move the code to GitHub so that some common functionality that might be applicable to many applications can be further added and expanded.

So how would you use something like this?
Following our example, first, attach the behavior to the User Model.
Secondly, add the Observer component to your relevant i.e. Users Controller.

And really that’s all there is to it, you’ll notice a bunch of methods in the component that look quite similar to the model callbacks. For example if you wanted to send out an email after the User is saved, you’ll place the appropriate code inside the modelAfterSaving() method of the Observer component… and so on, and so forth for any and each of the other implemented methods.

Also, you can use the sekeleton component to easily build your own to handle any specific task that you might require of your observer. Remember that you can (and should) have multiple observers based on their own responsibilities.

Well, that’s about it folks. I hope you’ll find these little code snippets useful and get inspired to use such an approach to make your application even more flexible and well-oiled.

P.S. If you have questions or would like more concrete examples, please don’t hesitate to ask, and I’ll be glad to do a follow up.

The Behavior:

<?php
class ObservableBehavior extends ModelBehavior {
    
    public $observers = array();
    
    public $implementableMethods = array('beforeSave'   => 'modelBeforeSaving',
                                         'afterSave'    => 'modelAfterSaving',
                                         'beforeFind'   => 'modelBeforeFinding',
                                         'afterFind'    => 'modelAfterFinding',
                                         'beforeDelete' => 'modelBeforeDeleting',
                                         'afterDelete'  => 'modelAfterDeleting');
                                       
    public function setup(&$Model, $settings) {
      if (!isset($this->settings[$Model->alias])) {
          $this->settings[$Model->alias] = array();
      }        
      $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], (array) $settings); 
    }
    
    public function addObserver(&$Model, $observer) {
      $args = func_get_args(2); 
      $observer = $args[2];
                   
      array_push($this->observers, $observer);      
    }
    
    public function beforeFind() {
      return $this->notifyObservers();
    }
    
    public function beforeSave() {
      return $this->notifyObservers();
    } 
    
    public function afterFind() {
      return $this->notifyObservers();
    }
    
    public function afterSave() {
      return $this->notifyObservers();
    }
    
    public function beforeDelete() {
      return $this->notifyObservers();  
    }
    
    public function afterDelete() {
      return $this->notifyObservers();  
    }
    
    private function notifyObservers() {
      $backtrace = debug_backtrace();
      $callingFunction = $backtrace[1]['function'];
      
      $valid = TRUE; 
      
      foreach($this->observers as $observer) {               
        $valid = $valid && $observer->{$this->implementableMethods[$callingFunction]}();    
      }
      
      var_dump($valid);
      return $valid; 
    }
     
}
?>

The component:

<?php
class ObserverComponent extends Object {
        
    public function startup(&$controller) {                
        $this->controller =& $controller;
        $this->Model =& $controller->{$controller->modelClass};               
        $this->Model->addObserver($this->Model, $this);
    }    
    
    public function modelBeforeFinding() {        
      echo '<p>beforeFind</p>';
      
      return TRUE;
    }
    
    public function modelAfterFinding() {
      echo '<p>afterFind</p>';
      
      return TRUE;
    }
    
    public function modelBeforeSaving() {
      echo '<p>beforeSave</p>';
      
      return TRUE;
    }
    
    public function modelAfterSaving() {
      echo '<p>afterSave</p>';
      
      return TRUE;
    }
    
    public function modelBeforeDeleting() {
      echo '<p>beforeDelete</p>';
      
      return TRUE;
    }
    
    public function modelAfterDeleting() {
      echo '<p>afterDelete</p>';
      
      return FALSE;
    }
} 
?>

14 thoughts on “Observer Pattern the CakePHP way

  1. Note that if you are using PHP 5.1 and up, the PHP SPL comes bundled with Observer and Subject classes.

    http://www.php.net/~helly/php/ext/spl/interfaceSplObserver.html

    One comment: Trying to confirm logical flow of callbacks using echo statements can cause you a bit of a headache, especially when you realize that CakePHP uses output buffering in a few places. You’re better off using $this->log(), or even debug(). Better yet, a test case ;-).

    Nice & simple implementation, nonetheless.

  2. I like the idea and the clean implementation. However, there seems to be a shortcoming that would make this fall short of useful for me.

    How does the observer get instantiated and able to register with the behavior in any request except those from the UsersController for the User model and the PostsController for the Post model (and so on)? Are events being sent even when you do something like $this->User->Post->save()?

    As far as I know this does not happen and is the real head-scratcher for any type of events or observers in Cake.

  3. I like the idea of this. I’d love to see some practical examples of how to implement the observable pattern.

  4. @Joel Perras

    Definitely will be adding test cases, when it becomes more mature in my own implementation. I’ve left echo and var_dump() on purpose so that implementation doesn’t have any “left overs” even with debug 0, since this is a structure only approach and not out-of-the-box solution.

    @Martin Westin

    If you attach both the behavior and the component in App Model, and App Controller respectively, the events will be registered on all models.

    So if you do $this->User->Post->find(‘first’); $this->User->save($stuff); for example, appropriate events will be triggered.

  5. @fly2279

    In the near future I will do a follow-up with some practical implementation.

    In the mean time you can easily try to attach the behavior and component in your App Model/Controller.

    Then try to save any model, you’ll notice that the beforeSave() and afterSave() callbacks will fire events with the registered observer. In the modelAfterSaving() method instead of echo, you can place some real-world code such as sending out an email to confirm that the model was saved successfully.

  6. Very interesting article teknoid. (Hmmm, observable Comments? Do I get mailed flowers on save?!) Ironically, I was just reading over something seemingly similar – in terms of handling events – (also ironically posted by Martin Westin in the Google Groups) by Kjell (cakealot.com) called Eventful, a plugin for CakePHP to register events.

    Perhaps you two may want to share some ideas? If developed fully, this could be a very powerful thing for any app being built. I’d also like to see some further follow-ups if/when you’re ready to showcase this more. Thanks!

  7. @teknoid
    Thanks for the explanation. I didn’t think Cake had that much “magic”. Looking at the Component’s startup method I assumed it would just pick the “main” model of the current controller.

    @Brendon Kozlowski
    I have had Eventful on my computer a while now but haven’t gotten around to testing it out fully. Looks nice though, doesn’t it.

  8. @Martin Westin: I believe both of these solutions look nice! ;-) I only quickly read over Kjell’s system on Monday, and teknoid’s yesterday; I haven’t had a chance to full grasp the ideas of either of them yet (nor have I even downloaded the code). However, the ideas behind them are very powerful and beneficial. It’s easy to see the possibilities.

  9. Teknoid,

    I like this implementation of the Observer pattern. I’m not sure about the part where the Observer adds itself to the Observable, seems a bit limited, but definitely is good use of the callback and is nicely “tucked away”.

    When continuing on this, consider using an abstract class to inherit from for the component. This will have some benefits.

    First, it you can define the startup() callback in there, so it would be even more tucked away. Second, you can define all those observer methods first (like: modelAfterFinding(), modelAfterDeleting()), so you don’t have to define them every time you make a new observer which might only need modelBeforeFinding(). Then simply:


    App::import('Component', 'Observer');

    class ObserverComponent extends ObserverComponent {

    public function modelBeforeFinding() {
    echo 'beforeFind';
    return TRUE;
    }

    }

    Perhaps tucking it into a plugin (packaging) would be a good idea.

  10. Oh, just noticed my example extends itself, that won’t work of course… i meant:

    class MyObserverComponent extends ObserverComponent { }

    Where ObserverComponent is the abstract class.

  11. A good article and food for thought.

    I’m not convinced, however, that the example is the best. Using observable behaviour would appear to remove forced sequentiality and dependence – attributes that in some cases are essential. It’s no use, for example, buying flowers when you failed to capture the address to send them to.

    On the other hand I just wrote a revision control module that would have been considerably easier if I’d had time to make myself aware of this article beforehand!

    Thanks.

  12. @Leo

    I think what you are talking about is slightly beyond the scope of the article, because Observable pattern is always the same…
    Something is being observed, and some things are observing it (extremely geeky porn).

    Anyway, cake gives us the power to fully leverage model relations in order to ensure that capture of the address will always take place, before the actual purchase (as in the example)…

    Following the example further, in this scenario, there’s no need to observe the Address model at all… Simple validation (and application design) will ensure that we have the address, before any sort of financial transaction can take place. In this case, the Purchase model would be the one being observed, and the observers would flip app switches whenever the purchase is… complete or denied (or a plethora of other conditions depending on the back-end) .

    Anyways, thanks for bringing up a very good point, and I think that some real-world example is long overdue…

  13. http://us.php.net/Reflection , for php5 would be a better idea than using debug_trace … I think. Not portable on PHP4 (but anway debug_trace is available after php 4.3 so I guess is not an issue if you change that, most of the php4 env have php4.2 anyway).

  14. @dragos

    Yeah, using Reflection method is a better approach.
    Some might argue that it is a bit more overhead, but since I always preach not to worry about things like that in favor of cleaner code… I agree with your suggestion.

Leave a comment