Jump to content

Create hooks using observer pattern


dpacmittal

Recommended Posts

I don't know whether I'll get better answer posting this in 'Php coding' or 'Application design', so I'll just post it in 'PHP coding' since it has more traffic.

 

Okay, so I've read about observer pattern, seen examples. Seen how to use SPL for the purpose. However, all the examples were basic and only dealt with a single hook. However, I'd like to have multiple hooks within a class and I am confused about whats the best way to achieve that.

I've written a code last night which does this but I'm afraid its very noobish. I'd be grateful to anyone who could point me in the right direction.

 

Code I wrote last night:

<?php
class Login implements SplSubject {
    private $storage;
    var $currhook;
    function __construct() {
        $this->storage = array();
    }

    function attach( SplObserver $observer ) {
        if(!isset($this->storage[$observer->hookName]))
            $this->storage[$observer->hookName] = new SplObjectStorage();
        $this->storage[$observer->hookName]->attach($observer);
    }
    function detach( SplObserver $observer) {
        $this->storage[$observer->hookName]->detach($observer);
    }
    function notify() {
        foreach ( $this->storage[$this->currhook] as $obs ) {
            $obs->update( $this );
        }
    }
    function log_in(){
        $this->currhook = 'beforelogin';
        $this->notify();
        echo "LOGGED IN";
        $this->currhook = 'afterlogin';
        $this->notify();
    }
}
class LoginObserver implements SplObserver {
    var $hookName;
    function update(SplSubject $login){
        $this->doupdate($login);
    }
    
}
class afterLogin extends LoginObserver {
    function __construct(Login $login){
        $this->hookName = 'beforelogin';
        $login->attach($this);
    }
    function doupdate($login){
        echo "beforelogin<br/>";
    }
}
class beforeLogin extends LoginObserver {
    function __construct(Login $login){
        $this->hookName = 'afterlogin';
        $login->attach($this);
    }
    function doupdate($login){
        echo "<br/>After login";
    }
}
$login = new Login();
new beforeLogin($login);
new afterLogin($login);
$login->log_in();
?>

 

Pardon the naming inconsistencies.

Link to comment
Share on other sites

Here is my take on it:

 

<?php
class Login implements SplSubject
{
    const STATE_BEFORE_LOGIN = 'beforeLogin';
    const STATE_AFTER_LOGIN = 'afterLogin';
    const STATE_FAILED_AUTH = 'failedAuth';

    private $_storage;
    private $_state;

    public function __construct()
    {
        $this->_storage = new SplObjectStorage();
    }

    public function attach(SplObserver $observer)
    {
        $this->_storage->attach($observer);
    }

    public function detach(SplObserver $observer)
    {
        $this->_storage->detach($observer);
    }

    public function notify()
    {
        foreach ($this->_storage as $observer) {
            $observer->update($this);
        }
    }

    public function getState()
    {
        return $this->_state;
    }

    public function authenticate($username, $password)
    {
        $this->_state = self::STATE_BEFORE_LOGIN;
        $this->notify();
        if ($username == 'test' && $password == 'hello') {
            $this->_state = self::STATE_AFTER_LOGIN;
            $res = true;
        }
        else {
            $this->_state = self::STATE_FAILED_AUTH;
            $res = false;
        }

        $this->notify();

        // possibly do other stuff here as well
        return $res;
    }
}

class PostLoginHook implements SplObserver
{
    public function update(SplSubject $login)
    {
        if ($login->getState() !== Login::STATE_AFTER_LOGIN) return;

        echo 'Doing some magical stuff after login.' . PHP_EOL;
    }
}

class PreLoginHook implements SplObserver
{
    public function update(SplSubject $login)
    {
        if ($login->getState() !== Login::STATE_BEFORE_LOGIN) return;

        echo 'Doing some magical stuff before login.' . PHP_EOL;
    }
}

class FailedLoginHook implements SplObserver
{
    public function update(SplSubject $login)
    {
        if ($login->getState() !== Login::STATE_FAILED_AUTH) return;

        echo 'Doing some magical stuff when someone provides invalid credentials.' . PHP_EOL;
    }
}

$login = new Login();

// attach observers
$login->attach(new PreLoginHook());
$login->attach(new PostLoginHook());
$login->attach(new FailedLoginHook());

// do stuff
$login->authenticate('hello', 'world');
$login->authenticate('test', 'hello');
?>

 

Output:

Doing some magical stuff before login.
Doing some magical stuff when someone provides invalid credentials.
Doing some magical stuff before login.
Doing some magical stuff after login.

 

Basically, the subject is not supposed to know anything about its observers. It shouldn't care who they are, just that they for whatever reason want to be updated when the subject thinks that something happened. The way you can achieve this is by storing the subject's state and provide access to it via the interface. This way, the observers can choose to act accordingly to that state. In my example, each observer acts on one specific state of the object.

 

Edit: One drawback of using this method is that the getStatus method isn't guaranteed to be available by the type hinting. In that case it might be better to define your own interfaces. Here is the example again modified to portray this (same output of course):

<?php
class Login
{
    const STATE_BEFORE_LOGIN = 'beforeLogin';
    const STATE_AFTER_LOGIN = 'afterLogin';
    const STATE_FAILED_AUTH = 'failedAuth';

    private $_storage;

    public function __construct()
    {
        $this->_storage = new SplObjectStorage();
    }

    public function attach(LoginHook $observer)
    {
        $this->_storage->attach($observer);
    }

    public function detach(LoginHook $observer)
    {
        $this->_storage->detach($observer);
    }

    public function notify($state)
    {
        foreach ($this->_storage as $observer) {
            $observer->update($this, $state);
        }
    }

    public function authenticate($username, $password)
    {
        $this->notify(self::STATE_BEFORE_LOGIN);
        if ($username == 'test' && $password == 'hello') {
            $this->notify(self::STATE_AFTER_LOGIN);
        }
        else {
            $this->notify(self::STATE_FAILED_AUTH);
        }

        // possibly do other stuff here as well
    }
}

interface LoginHook
{
public function update(Login $login, $state);
}

class PostLoginHook implements LoginHook
{
    public function update(Login $login, $state)
    {
        if ($state !== Login::STATE_AFTER_LOGIN) return;

        echo 'Doing some magical stuff after login.' . PHP_EOL;
    }
}

class PreLoginHook implements LoginHook
{
    public function update(Login $login, $state)
    {
        if ($state !== Login::STATE_BEFORE_LOGIN) return;

        echo 'Doing some magical stuff before login.' . PHP_EOL;
    }
}

class FailedLoginHook implements LoginHook
{
    public function update(Login $login, $state)
    {
        if ($state !== Login::STATE_FAILED_AUTH) return;

        echo 'Doing some magical stuff when someone provides invalid credentials.' . PHP_EOL;
    }
}

$login = new Login();

// attach observers
$login->attach(new PreLoginHook());
$login->attach(new PostLoginHook());
$login->attach(new FailedLoginHook());

// do stuff
$login->authenticate('hello', 'world');
$login->authenticate('test', 'hello');
?>

Link to comment
Share on other sites

Thanks for the code.

 

I too had thought of doing this way but I wanted to have better performance. In this way, you notify all the attached observers which then process through a series of conditions. This creates unnecessary overhead. Is there any way we can notify only the objects which are related to a particular state?

 

Link to comment
Share on other sites

That would break encapsulation. I don't think this overhead would be all that significant.

Yeah, I hope so.

 

Well, the more you abstract things, the more performance you sacrifice. That's just the way things work. If performance is the single-most important thing in your application then write it in assembly.

I'd rather sacrifice performance. :)

 

Thanks for all the replies. I'll mark this solved.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.