Emesary: PHP example of class based inter-object communication

To take a high level view, any system is built from a collection of parts - some of these parts need to perform a required job (the primary purpose) and the parts will need to interface to each other.

Taking a traditional three tier system as an example: The database tier needs a defined API, after all accessing the database is the primary purpose. This is where a set of classes with methods and properties is required. During the operation of the database tier it may cause exceptions or failures to be raised. Also the database may need to communicate externally to any number of services that are not necessarily known at build time.

Building on a scheme for communicating simply between objects, I've been asked to provide an example to clarify things for those of us who read code better than we read prose.

Classifying the messages

Firstly what we will need to do is to setup a small class that really is a list of constants - this is purely to aid with marshalling messages. By grouping messages it is easy to quickly filter based on what the message recipients declare as being their interests.

class SystemIdent
{
    const Database         = 0x1000;
    const Audit            = 0x1001;
    const Exec             = 0x1002;
    const Authentication   = 0x1003;
};

Defining the message base class

By design all messages sent via the event bus need to be derived from the Message class. This allows a reasonable amount of sanity checking, and forces a structure onto the use of this system. Experiments have been made in using more anonymous messages, however the lack of embedded message types and system idents preclude the use of any intelligent routing or handling within the transmitter class.

class Message 
{
    private $message_type;
    private $system_ident; // from SystemIdent class

    static $regid = 2;
    static $reg_desc = array();

    function __construct($message_type, $system_ident)
    {
        $this->system_ident = $system_ident;
        $this->message_type = $message_type;
    }

    public function get_system_ident()
    {
        return $this->system_ident;
    }

    public function get_message_type()
    {
        return $this->message_type;
    }

    static function register_message_type($id)
    {
        $reg_desc[Message::$regid] = $id;
        return Message::$regid++;
    }
};

Whilst we can communicate with this message class it is more normal to derive from it to add new methods and properties that are relevant to the message being sent. Normally this is good - the only time that we need to be a little careful is to avoid embedding objects into the class - as these are hard to use with the Inter-Process bridge that I will describe later.

Receiver interface

This is the heart of the system and where the interface that all classes wishing to receive messages should implement.

interface Receiver
{
    const OK = 0;   			// info
    const Fail = 1; 			// if any item fails then send message may return fail
    const Abort = 2; 			// stop processing this event and fail
    const Finished = 3; 		// stop processing this event and return success
    const NotProcessed = 4; 	// recipient didn't recognise this event

    // Returns a bitmask for the message notification types which are required. This allows a fairly coarse control
    // by the transmitters of which messages to send to which recipients. Return value of zero means all types required
    function get_RequiredNotificationTypes();

    // main message receiver
    function receive($message);
}

Transmitter

Transmits Message-derived objects. Each instance of this class provides a event databus which any number of receivers can attach to. Messages may be inherited and customised between individual systems.

class Transmitter
{
    private $receiver_list = array(); // contains objects of type Recevier

    function Transmitter()
    {
    }

    // Registers a class to receive messsages from this transmitter. A class can be registered with any number of transmitters
    function register($receiver)
    {
        if (!in_array($receiver, $this->receiver_list))
            $this->receiver_list [] = $receiver;
    }

    // remove a receiver
    function de_register($receiver)
    {
        if (in_array($receiver, $this->receiver_list))
        {
            unset($this->receiver_list[array_search($receiver, $this->receiver_list)]);
        }
        return 0;
    }

    /*
     * Notifies all registered classes of the message.
     */
    function notify_all($message)
    {
        foreach ($this->receiver_list as $key => $value)
        {
            $rv = $value->receive($message);
            switch ($rv)
            {
            case Receiver::Fail;
            case Receiver::NotProcessed;
            case Receiver::OK;
            break;

            case Receiver::Abort;
                return Receiver::Fail;
            break;

            case Receiver::Finished;
                return Receiver::OK;
            break;
            }

        }
        return Receiver::OK;
    }

};

Example of usage

This is a simple example - but based on a real world problem, and one that it solves nicely.

//
// this is just a stub to demonstrate the concept, it is an empty shell that would
// normally be connected to a database table.
class CbfDbEntity
{
    var $fields = array();

    function CbfDbEntity($table, $primary_key)
    {
    }

    function write_record()
    {
        foreach ($this->fields as $k => $v)
            echo (" $k => $v,");
        echo "\n";
        unset($this->fields);
    }

    function read_record()
    {
    }

    function set_field($f, $v)
    {
        $this->fields[$f]=$v;
    }

};

// class to demonstrate a Receiver. The design of the event bus is such that a class will implement the receiver interface
// which nicely allows the class to receive messages, without disrupting its real job.
// By implementing the interface and registering with the global event bus this class will receive all messages sent on the
// bus. It is upto the class to determine the required filtering and which messages to act upon. This presents a nice design
// that is easily extended.
class Mrt extends CbfDbEntity implements Receiver
{
    var $id;

    function Mrt($id)
    {
        parent::CbfDbEntity("audit","audit_id");

        $this->id = $id;
    }

    function get_RequiredNotificationTypes(){ return 0; }

    function receive($message)
    {
        print "rcv: {$this->id} : ty:{$message->get_message_type()} sys:{$message->get_system_ident()}\n";
        if ($message->get_message_type() == AuditLogMessage::$id)
        {
            print "AuditLog Message\n";
            $this->set_field("id",$this->id);
            $this->set_field("action",$message->action);
            $this->set_field("additional",$message->additional);
            $this->set_field("user",$message->user);
            $this->write_record();
        }

        return 0;
    }

};

//
// A specifc message - based on the Message object - but with extra records. 
class AuditlogMessage extends Message
{
    var $type, $action, $additional, $user;
    static $id=-22;

    function AuditlogMessage($type, $action, $additional, $user="")
    {
        parent::Message(AuditLogMessage::$id, SystemIdent::Audit);

        $this->type = $type;
        $this->action = $action;
        $this->additional = $additional;
        $this->user = $user;
    }
};
AuditLogMessage::$id = Message::register_message_type("AuditLog"); // register and get a unique ID.

$eventBus = new Transmitter();

$mrt = new Mrt("id1");
$ddd = new Mrt("ddd");

echo "-- Test notification with two recipients\n";
$eventBus->register($mrt);
$eventBus->register($ddd);

$eventBus->notify_all(new Message(0,SystemIdent::Exec));
$eventBus->de_register($mrt);

echo "\n-- Test notification with one recipient\n";
$eventBus->notify_all(new Message(1,SystemIdent::Exec));

echo "\n-- Test nofication of AuditLogMessage with one recipient\n";
$msg = new AuditLogMessage("Intrusion","Logon","Additional info");
$eventBus->notify_all(new AuditLogMessage("Intrusion","Logon","Additional info"));

Event intercommunication source file download

Download source code for Event intercommunication example