PHP Event-Listener Best-Practice Implementation

PHP Event-Listener best-practice implementation

Well, there's really three different ways of doing this from an implementation perspective (note that these are OO design patterns, but you could implement them functionally or procedurally if you wanted to).

1. Observer Pattern

You can implement the Observer Pattern. Basically, you'd have each thing that can raise events be a subject. Then the classes/code you want to listen binds to what it wants to listen to specifically. So let's say you have a controller called Foo. If you wanted to listen to it, you could call $fooController->attach($observer);. Then, whenever the controller wanted to say something, it would dispatch the event to all of the observers.

This is really well suited for a notification system (to extend what classes are doing). It's not as well suited for modifying the behavior of code in real time.

2. Decorator Pattern
You can also implement the Decorator Pattern. Basically, you take the object that you want to modify, and "wrap" it in a new object that does what you want to change. This is really well suited for modifying and extending the behavior (since you can selectively override functionality from the wrapped class).

This works very well if you have defined interfaces and expect objects to conform to them. If you don't have interfaces (or don't use them properly), most of what the decorator pattern can do for you will be lost.

Also note that this really isn't a way of doing events, it's a way of modifying object behavior.

3. Mediator Pattern

You could also use a Mediator. Basically, you'd have one global mediator that keeps track of your listeners. When you want to trigger an event, you send the event to the mediator. The mediator can then keep track of which listening objects want to receive that event, and pass the message along properly.

This has the advantage of being central. Meaning multiple senders can send the same event, and to the listeners it doesn't make a difference who sent it...

I expanded on this topic in a blog post.

How to implement event listening in PHP

You can solve this problem using ZeroMQ.

ZeroMQ is a library that provides supercharged sockets for plugging things (threads, processes and even separate machines) together.

I assume you're trying to push data from the server to the client. Well, a good way to do that is using the EventSource API (polyfills available).

client.js

Connects to stream.php through EventSource.

var stream = new EventSource('stream.php');

stream.addEventListener('debug', function (event) {
var data = JSON.parse(event.data);
console.log([event.type, data]);
});

stream.addEventListener('message', function (event) {
var data = JSON.parse(event.data);
console.log([event.type, data]);
});

router.php

This is a long-running process that listens for incoming messages and sends them out to anyone listening.

<?php

$context = new ZMQContext();

$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind("tcp://*:5555");

$pub = $context->getSocket(ZMQ::SOCKET_PUB);
$pub->bind("tcp://*:5556");

while (true) {
$msg = $pull->recv();
echo "publishing received message $msg\n";
$pub->send($msg);
}

stream.php

Every user connecting to the site gets his own stream.php. This script is long-running and waits for any messages from the router. Once it gets a new message, it will output this message in EventSource format.

<?php

$context = new ZMQContext();

$sock = $context->getSocket(ZMQ::SOCKET_SUB);
$sock->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "");
$sock->connect("tcp://127.0.0.1:5556");

set_time_limit(0);
ini_set('memory_limit', '512M');

header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");

while (true) {
$msg = $sock->recv();
$event = json_decode($msg, true);
if (isset($event['type'])) {
echo "event: {$event['type']}\n";
}
$data = json_encode($event['data']);
echo "data: $data\n\n";
ob_flush();
flush();
}

To send messages to all users, just send them to the router. The router will then distribute that message to all listening streams. Here's an example:

<?php

$context = new ZMQContext();

$sock = $context->getSocket(ZMQ::SOCKET_PUSH);
$sock->connect("tcp://127.0.0.1:5555");

$msg = json_encode(array('type' => 'debug', 'data' => array('foo', 'bar', 'baz')));
$sock->send($msg);

$msg = json_encode(array('data' => array('foo', 'bar', 'baz')));
$sock->send($msg);

This should prove that you do not need node.js to do realtime programming. PHP can handle it just fine.

Apart from that, socket.io is a really nice way of doing this. And you could connect to socket.io to your PHP code via ZeroMQ easily.

See also

  • ZeroMQ
  • ZeroMQ PHP Bindings
  • ZeroMQ is the Answer - Ian Barber (Video)
  • socket.io

zf2 mvc event-listener or strategy

There's a ton of approaches you could use here, but sticking to your original question, it's quite easy to inject things into your layout model, with something like this:

Module.php

/**
* On bootstrap event
*
* @param \Zend\Mvc\MvcEvent $e
*/
public function onBootstrap(MvcEvent $e)
{
// Inject something, like a nav into your Layout view model
$viewModel = $e->getViewModel(); // Layout View Model
$navigation= new ViewModel(array(
'username' => 'Bob' // Dynamically set some variables..
));
$navigation->setTemplate('navigation/mynav');
$viewModel->addChild($navigation, 'navigation');
}

You could also create a custom view Helper to do the work for you if you wanted

<?php
/**
* MyHelper.php
*/
namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceManagerAwareInterface;
use Zend\ServiceManager\ServiceManager;

class MyHelper extends AbstractHelper implements ServiceManagerAwareInterface
{
/**
* Invoke
*
* @return string
*/
public function __invoke()
{
// Dynamically build your nav or what ever.

$patientService = $this->getServiceManager()->get('PatientService');

return 'soemthing';
}

/**
* @var ServiceManager
*/
protected $serviceManager;

/**
* Retrieve service manager instance
*
* @return ServiceManager
*/
public function getServiceManager()
{
return $this->serviceManager;
}

/**
* Set service manager instance
*
* @param ServiceManager $locator
* @return User
*/
public function setServiceManager(ServiceManager $serviceManager)
{
$this->serviceManager = $serviceManager;
return $this;
}
}

Observer Pattern in PHP - Correct Practice for Different Events

I don't think you're implementation is wrong, but it just doesn't sit right with me. Your Observable has to know the events handler functions and implement them (without an interface of some kind to guarantee they are there. To continue the way you're going I'd make sure anything that would be attached to Login should implement an interface that guarantees those action functions will be there. From there, Login doesn't have to learn anything about the observer, it just calls the functions.

Another approach I would take would be to specific the handler for event names, like so:

class Observable {
protected static $event_names = array();
protected $observers = array();

function __construct() {
foreach (static::$event_names as $event_name) {
$this->observers[$event_name] = array();
}
}

function register($event, $object, $handler) {
if (array_key_exists($event, $this->observers)) {
$this->observers[$event][] = array($object, $handler);
} else {
echo "Invalid event \"$event\"!";
}
}

function trigger($event, $data = null) {
foreach ($this->observers[$event] as $observer) {
$observer[0]->$observer[1]($data);
}
}
}

class Login extends Observable {
protected static $event_names = array("userLoggedIn", "userLoggedOut", "userRegistered");
}

And then you're observers would register for events like so:

class SomeListener {
function __construct() {
$login_instance->register("userLoggedIn", $this, "myLoggedInHandler");
}

function myLoggedInHandler($data = null) {
echo "User Logged In.";
}
}

That's just an opinion though, it's a different approach but then it only requires the knowledge of what events fire (which your method could be argued to do as well, but the Observable should simply just call the handler methods IMO).

Is it bad practice to use the same event-listener callback for multiple Event types?

I think it depends on how many events you have, and how similar their effects are. If both the event types are semantically similar (e.g. MOUSE_DOWN and MOUSE_CLICK) and their effect is similar (e.g. press a button), then you could bundle them.

If the effects of the events are too different (I'd say less than 80% identical code) I would advise against it, then just write multiple handlers and group the common functionality in a separate subroutine.

JavaScript & Events - Best Practice

This is an inline event handler attribute. (+1 chjj's answer for alternatives). It's generally considered ‘bad’ for a number of reasons:

  • you're mixing small pieces of JavaScript syntax inline with HTML syntax:

    • when you have lots of these, especially when you have lots of elements that all contain essentially the same bit of code, it's harder to read and maintain the code;

    • you get nested-escaping horrors when you need to use special-to-HTML characters in your code:

eg.:

<a href="x.html" onclick="this.innerHTML= '<em>I like "fish &amp; chips"</em>';">
  • properties of the target element and all ancestor nodes become variables, potentially hiding your own variables of the same name. See this question for background on this surprising and almost always unwanted behaviour;

    • this introduces spooky incompatibilities as browsers have different DOM properties;

    • and future browsers introducing new properties will potentially break your code.



Related Topics



Leave a reply



Submit