Tags

, , ,

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;
    }
}
?>