MinimalBus

A minimal implementation of SimpleBus in PHP

The code below is the simplest implementation - in PHP - I could think of that would still have the benefits of the SimpleBus design. MinimalBus is usable, but it is quite wordy.

<?php
/*
	A minimal implemenation of the simplebus specification in PHP.
	The message format is a native array/string/number construct 
	limited by what is supported in JSON.
	The filter format is based on a template construct extended 
	with regular expressions.
	The content of the message is undefined and no meta data is added 
	by the bus itself. 
	The participating components must identify themselves when 
	publishing as well as listening so the bus will not send a message 
	to the listeners of the originating participant.
*/

interface minimalBusInterface {
	
	public function subscribe( $participant, $filter=null, $callback );
	
	public function publish( $participant, $message );
	
	public function unsubscribe( $listenerId );
}

/*
	this is a minimal implementation of the localbus interface
*/
class minimalBus implements minimalBusInterface {

	private $listeners = array();

	public function subscribe( $participant, $filter=null, $callback ) {
		$listenerId = uniqid();	// create a unique non-guessable 
					// listenerId with which an object can 
					// later remove a listener
		while ( array_key_exists( $listenerId, $this->listeners ) {
			$listenerId = uniqid();
		}
		$this->listeners[ $listenerId ] = array(
			'participant' => $participant,
			'filter' => $filter,
			'callback' => $callback
		);
		return $listenerId;
	}
	
	public function publish( $participant, $message ) {
		// get rid of any non standard data types or references
		$message = json_decode( json_encode( $message ), true ); 
		foreach ( $this->listeners as $listener ) {
			// don't send messages to the originating participant 
			if ( $listener['participant'] != $participant 
				&& $this->matches( $message, $listener['filter'] ) ) {
				$listener['callback']( $json );
			}
		}
	}
	
	public function unsubscribe( $listenerId ) {
		unset( $this->listeners[ $listenerId ];
	}
	
	/*
		this method implements a simple filtering mechanism based on
		message templates expanded with regular expressions. 
		If a value is a string starting with a '/', it is assumed to
		be a regular expression
		if a value is '*' it matches any value in the message at that 
		point, no matter the type
		
		array( 'feed' => array( 'url' => '*' ) )
		array( 'feed' => array( 'url' ) )
		[ 'feed' => [ 'url' ] ] 
		'{ feed : [ url ] }' 
	*/
	protected function matches( $message, $filter ) {
		if ( is_string( $filter ) ) {
			// change a json encoded filter back to native array type
			$filter = json_decode( $filter, true );
		}
		foreach ( $filter as $key => $value ) {
			if ( is_numeric( $key ) ) { 
				// [ 'x' => [ 'y' ] ] is mapped to 
				// [ 'x' => [ 'y' => '*' ] ]
				$key = $value;
				$value = '*';
			}
			if ( !array_key_exists( $key , $message ) ) { 
				// if the key doesn't exist, fail immediately
				return false;
			}
			if ( is_array( $value ) ) { 
				if ( !is_array( $message[$key] ) ) { 
					// type doesn't match, so fail
					return false;
				}
				if ( !$this->matches( $message[$key], $value ) ) { 
					// recurse and return false if and only if 
					// matches() returns false
					return false;
				}
			} else {
				if ( is_string( $value ) && $value[0] == '/' ) { 
					// match a value with a regexp
					if ( !preg_match( $value, $message[$key] ) ) {
						return false;
					}
				} elseif ( $value != '*' && !$message[$key]==$value ) {
					// match an exact value
					return false;
				}
			}
		}
		return true;
	}
}

blog comments powered by Disqus