Java-like events in PHP

This article introduces a PHP implementation of the Java event schema. The implementation demonstrates how to use the robust and typed Java-like events in PHP.

In my previous article I summarized event system definitions and showed an example of PHP event model, which is built with the call_user_func() function. This acticle expands the subject by introducing an advanced PHP event model that is based on sender/eventObject/listener collaboration, which was popularized by the Java world.

The following is an example of Java event system. The example is based on the shorten and reformatted versions of the ProtocolCommandSupport.java, ProtocolCommandListener.java and ProtocolCommandEvent.java files from Apache.org subversion repository, which contains the source code of projects, developed by the Apache Group.

ProtocolCommandSupport.java contains a class that fires events–an event producer. Instances of this class are created by the system and, when system operates with the instances, they fire the corresponding events. Actually, this class seems to be just a container for listeners–classes that listen to the event producer and react when they are notified about the particular fired event. Pay attention to the “addProtocolCommandListener” and “removeProtocolCommandListener” methods, they are used to attach and detach listeners. Two other methods, “fireCommandSent” and “fireReplyReceived”, invoke methods of the objects that are stored in the “__listeners” collection. This collection, actually, stores only ProtocolCommandListener objects, because only the objects of this type can be passed when calling the “addProtocolCommandListener” and “removeProtocolCommandListener” methods.

ProtocolCommandSupport.java

public class ProtocolCommandSupport implements Serializable {

	private Object __source;
	private ListenerList __listeners = new ListenerList();

	public ProtocolCommandSupport(Object source) {
		__source = source;
	}

	/***
	 * Fires a ProtocolCommandEvent signalling the sending of a command to all
	 * registered listeners.
	 ***/
	public void fireCommandSent(String command, String message) {
		Enumeration en = __listeners.getListeners();
		ProtocolCommandEvent event = 
			new ProtocolCommandEvent(__source, command, message);
		ProtocolCommandListener listener;
		while (en.hasMoreElements()) {
			listener = (ProtocolCommandListener)en.nextElement();
			listener.protocolCommandSent(event);
		}
	}

	/***
	 * Fires a ProtocolCommandEvent signalling the reception of a command reply
	 * to all registered listeners.
	 ***/
	public void fireReplyReceived(int replyCode, String message) {
		Enumeration en = __listeners.getListeners();
		ProtocolCommandEvent event = 
			new ProtocolCommandEvent(__source, replyCode, message);
		ProtocolCommandListener listener;
		while (en.hasMoreElements()) {
			listener = (ProtocolCommandListener)en.nextElement();
			listener.protocolReplyReceived(event);
		}
	}

	public void addProtocolCommandListener(ProtocolCommandListener listener) {
		__listeners.addListener(listener);
	}

	public void removeProtocolCommandListener(ProtocolCommandListener listener) {
		__listeners.removeListener(listener);
	}

}

ProtocolCommandListener.java contains a Listener interface. The idea behind it is that any class, which should be subscribed on the ProtocolCommandSupport class events, should implement this interface and implementing this interface is enough to subscribe to the events. This makes possible creation of classes, which can subscribe to events of two or more different classes, because one class can implement multiple interfaces.

ProtocolCommandListener.java

public interface ProtocolCommandListener extends EventListener {

	/***
	 * This method is invoked by a ProtocolCommandEvent source after
	 * sending a protocol command to a server.
	 *
	 * @param event The ProtocolCommandEvent fired.
	 ***/
	public void protocolCommandSent(ProtocolCommandEvent event);

	/***
	 * This method is invoked by a ProtocolCommandEvent source after
	 * receiving a reply from a server.
	 *
	 * @param event The ProtocolCommandEvent fired.
	 ***/
	public void protocolReplyReceived(ProtocolCommandEvent event);

}

The last Java file in this article is ProtocolCommandEvent.java. This file contains a subclass, named ProtocolCommandEvent, of the EventObject class. Subclasses of EventObject are used to pass event data from an event producer to event listeners. The ProtocolCommandEvent class, additionally to the getSource() method, defined in EventObject, creates the getMessage(), isReply(), isCommand(), getReplyCode() and getCommand() methods. These methods only return class read-only fields, which can be set only in class constructor.

ProtocolCommandEvent.java
public class ProtocolCommandEvent extends EventObject {

	private int __replyCode;
	private boolean __isCommand;
	private String __message, __command;

	public ProtocolCommandEvent(Object source, String command, String message) {
		super(source);
		__replyCode = 0;
		__message = message;
		__isCommand = true;
		__command = command;
	}

	public ProtocolCommandEvent(Object source, int replyCode, String message) {
		super(source);
		__replyCode = replyCode;
		__message = message;
		__isCommand = false;
		__command = null;
	}

	public String getCommand() { return __command; }
	public int getReplyCode() { return __replyCode; }
	public boolean isCommand() { return __isCommand; }
	public boolean isReply() { return !isCommand(); }
	public String getMessage() { return __message; }

}

This class triad is a common implementation of the Java event system, which are strong-typed and time-proven event solution. And it may be a good idea to try this solution in PHP programming language, which currently supports interfaces and typed parameters, e.g. all necessary things for implementing it.

Summarizing the above, the following instruction can be used for creating a new event:

  1. Create a subclass of the EventObject class, which provides listeners with the event data. Usually its name is built using event name and “Event” suffix, like ProtocolCommandEvent in the example above. However, you can reuse the existing class, even EventObject itself.
  2. Create or extend an interface that listeners should implement. Usually the interface name ends with “Listener” suffix and contains one or more functions, which are called by the event producer, with one argument of the corresponding EventObject subclass.
  3. Add the “add<EventName>Listener(<EventName>Listener)” and “fire<EventName>()” methods to the event producer. Optionally, add the “remove<EventName>Listener(<EventName>Listener)” if you plan to remove listeners. Having such method is considered to be a good practice, but it can be omitted.
  4. Inject the calls of the “fire<EventName>()” method somewhere in the code of the producer, where you want this event to be fired. This method can be fired under the multiple circumstances. For example, DatabaseError event can be fired when a database connection cannot be created or when a query fails.

The PHP event implementation requires two related classes to be defined and included into the PHP project: EventObject and ListenerList, as follows:

class EventObject {

	protected $source;

	function __construct($source) {
		$this->source = $source;
	}

	function getSource() {
		return $this->source;
	}

}

class ListenerList {

	protected $listeners = array();

	function add($listener) {
		$this->listeners[] = $listener;
	}

	function getRaw() {
		return $this->listeners;
	}

}

Let’s create a sample class, which fires one event and, in the same time, litens to this event. It may sound strange, but it is a frequently used technique.

interface MyEventListener {
	function onMyEvent(EventObject $event);
}

class MyClass implements MyEventListener {

	protected $listeners;

	function __construct() {
		$this->listeners = new ListenerList();
		$this->addMyEventListener($this);
	}

	function addMyEventListener(MyEventListener $listener) {
		$this->listeners->add($listener);
	}

	function work() {
		echo "Working!\n";
		$this->fireMyEvent();
		echo "Working!\n";
		$this->fireMyEvent();
		echo "Working!\n";
	}

	protected function fireMyEvent() {
		$event = new EventObject($this);
		foreach ($this->listeners->getRaw() as $listener) {
			$listener->onMyEvent($event);
		}
	}

	function onMyEvent(EventObject $event) {
		echo "Taking short break...\n";
	}

}

$object = new MyClass();
$object->work();
// Will print:
//   Working!
//   Taking short break...
//   Working!
//   Taking short break...
//   Working!

The described event schema is a type-aware, that improves stability of your code. But please notice that it requires additional coding (more interfaces and event classes) and it is slower then previously described event schema.