Skip to content

Instantly share code, notes, and snippets.

@xsist10
Last active January 21, 2017 17:28
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xsist10/824b559c4effaf43ddb3 to your computer and use it in GitHub Desktop.
Save xsist10/824b559c4effaf43ddb3 to your computer and use it in GitHub Desktop.
An event dispatcher in a tweet
<?php
// Version 1
// Minified
// class Dispatch{function add($e,$l){$this->l[$e][]=$l;}function trigger($e,$d){foreach ($this->l[$e] as $l)call_user_func_array($l, $d);}}
class Dispatch{
function add($e, $l) {
$this->l[$e][] = $l;
}
function trigger($e, $d) {
foreach ($this->l[$e] as $l) {
call_user_func_array($l, $d);
}
}
}
class Greeter {
public function greet($name) {
echo "Hello $name\n";
}
}
$dispatch = new Dispatch();
$dispatch->add('greet', array(new Greeter, 'greet'));
$dispatch->trigger('greet', array('Bob'));
<?php
// Version 2
// Minified
// class Dispatch{function add($e,$l){$this->l[$e][]=$l;}function trigger($e,$d){foreach($this->l[$e] as$l)$l($d);}}
class Dispatch {
function add($e, $l) { $this->l[$e][]=$l; }
function trigger($e, $d) { foreach ($this->l[$e] as $l) $l($d); }
}
$dispatch = new Dispatch();
$dispatch->add('greet', function ($name) {
echo "Hello $name\n";
});
$dispatch->trigger('greet', 'Bob');
@matthiasnoback
Copy link

My event dispatcher (see https://twitter.com/matthiasnoback/status/519075878442381312) knows about priorities. It has one downside: calling it with unknown event names throws some warnings.

It is unit-tested though, using the unit-testing-framework in a tweet (https://gist.github.com/mathiasverraes/9046427).

<?php

function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n"; if(!$p){$GLOBALS['f']=1;}}function done(){if(@$GLOBALS['f'])die(1);}

class E
{
    function r($n, $c, $p)
    {
        $this->e[$n][$p][] = $c;
    }

    function d($n, $d)
    {
        $p = $this->e[$n];
        krsort($p);
        foreach ($p as $q)
            foreach ($q as $r)
                $r($d);
    }
}

// minified version
// class E{function r($n,$c,$p){$this->e[$n][$p][]=$c;}function d($n,$d){$p=$this->e[$n];krsort($p);foreach($p as$q)foreach($q as$r)$r($d);}}

ob_start();
$ed = new E();
$ed->r(
    'event',
    function () {
        echo 'event, priority 0;';
    },
    0
);
$ed->r(
    'event',
    function () {
        echo 'event, priority 10;';
    },
    10
);
$ed->r(
    'other_event',
    function () {
        echo 'other_event, priority -5;';
    },
    -5
);
$ed->r(
    'other_event',
    function () {
        echo 'other_event, priority 5;';
    },
    5
);
$ed->d('event', array());
$ed->d('other_event', array());
//$ed->d('invalid_event', array());
$output = ob_get_contents();
ob_end_clean();

it(
    'calls event listeners in the right order',
    $output === 'event, priority 10;event, priority 0;other_event, priority 5;other_event, priority -5;'
);

@mathiasverraes
Copy link

<?php
class E{function r($n,$c,$p){$this->e[$n][$p][]=$c;}function d($n){$p=$this->e[$n];krsort($p);array_walk_recursive($p,@call_user_func);}}

Two characters shorter, tests still pass. Functional programming bitches! I'll just blame php for the verbosity of the two function names at the end.

@matthiasnoback
Copy link

I've updated my version: I removed the foreach brackets. This created some space for the event data parameter.

@matthiasnoback
Copy link

And as suggested by @wouterj I also removed some whitespace in the foreach loop: foreach($p as $q) can be just foreach($p as$q).

@xsist10
Copy link
Author

xsist10 commented Oct 6, 2014

Like the priority adding. @mathiasverraes, can we wangle event data passing into yours? We might also now need an event-object-in-a-tweet, but that should be significantly simpler...

@matthiasnoback
Copy link

@xsist10 Event data is back! See the comment above.

@mathiasverraes
Copy link

There you go, 18 characters shorter than @matthiasnoback's latest version, so it clocks in at 120 characters, which leaves enough room for a hashtag. It also has event data passing, so it's fully featured. The unit test now also tests the data passing.

I've had to change the api a bit, but it's for the better: using e() will make each call significantly shorter than the verbose E class.

<?php

function e($n,$p,$c=0){static $a;@krsort($a[$n]);if($c)$a[$n][$p][]=$c;else foreach($a[$n] as$q)foreach($q as$r)$r($p);}


function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n"; if(!$p){$GLOBALS['f']=1;}}function done(){if(@$GLOBALS['f'])die(1);}
ob_start();
e(
    'event',
    0,
    function ($data) {
        echo "event, $data, priority 0;";
    }
);
e(
    'event',
    10,
    function ($data) {
        echo "event, $data, priority 10;";
    }
);
e(
    'other_event',
    -5,
    function ($data) {
        echo "other_event, $data, priority -5;";
    }
);
e(
    'other_event',
    5,
    function ($data) {
        echo "other_event, $data priority 5;";
    }
);
e('event', 'dataX');
e('other_event', 'dataY');
$output = ob_get_contents();
ob_end_clean();

it(
    'calls event listeners in the right order',
    $output === 'event, dataX, priority 10;event, dataX, priority 0;other_event, dataY priority 5;other_event, dataY, priority -5;'
);

@mathiasverraes
Copy link

✔︎ It is perfectly possible to inject the event dispatcher as a dependency into classes.

<?php
// (assume e() is included)

class Foo {
    private $eventDispatcher;
    function __construct($eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }
    function doStuff()
    {
        // ...
        call_user_func($this->eventDispatcher, "my_event", "my data");
    }
}

ob_start();
$foo = new Foo(@e);
e("my_event", 5, function($data) { echo "My event was called with '$data'";});
$foo->doStuff();
$output = ob_get_contents();ob_end_clean();

it(
    'is perfectly possible to inject the event dispatcher as a dependency into classes.',
    $output === "My event was called with 'my data'"
);

@matthiasnoback
Copy link

Trying a new version:

<?php
class E{function r($n,$c,$p){$this->{$n}[$p][]=$c;krsort($this->$n);}function d($n,$d){@array_walk_recursive($this->$n,call_user_func,$d);}}

This one combines the work of @mathiasverraes and reintroduces event data. However, the event listeners should ignore the first argument passed to them, since that will be the key of the corresponding array value ;) see http://nl1.php.net/manual/en/function.array-walk-recursive.php

@matthiasnoback
Copy link

Oooh that looks pritty nice, @mathiasverraes!

By the way, I really like this as an exercise since it exposes all kinds of hidden assumptions, like a preference for classes, and an aversion of using different function parameters to trigger different actions :)

@xsist10
Copy link
Author

xsist10 commented Oct 6, 2014

Nicked your update @mathiasverraes and used all that saved space you made to add event propagation control.

✔︎ It calls event listeners in the right order with event propagation

<?php

function e($n,$p,$c=0){static $a;@krsort($a[$n]);if($c)$a[$n][$p][]=$c;else foreach($a[$n] as$q)foreach($q as$r)if(!$r($p))return;}

function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n"; if(!$p){$GLOBALS['f']=1;}}function done(){if(@$GLOBALS['f'])die(1);}

ob_start();
e(
    'event',
    0,
    function ($data) {
        echo "event, $data, priority 0;";
        return true;
    }
);
e(
    'event',
    10,
    function ($data) {
        echo "event, $data, priority 10;";
        return true;
    }
);
e(
    'other_event',
    -5,
    // Should not reach this event due to propagation termination
    function ($data) {
        echo "other_event, $data, priority -5;";
        return true;
    }
);
e(
    'other_event',
    5,
    function ($data) {
        echo "other_event, $data priority 5;";
        return false; // Will stop event propagation after this event
    }
);
e('event', 'dataX');
e('other_event', 'dataY');
$output = ob_get_contents();
ob_end_clean();

it(
    'calls event listeners in the right order with event propagation',
    $output === 'event, dataX, priority 10;event, dataX, priority 0;other_event, dataY priority 5;'
);

@liuggio
Copy link

liuggio commented Oct 22, 2014

maybe you are interested on a something like a framework ... https://github.com/liuggio/sized140

@jm42
Copy link

jm42 commented Oct 31, 2014

Dependency injection container in a tweet ... https://gist.github.com/jm42/3c32dd50bb9d09f57c4a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment