Event Handling Classes in PHP

Wait a second - now we get into some serious hacking :wink:

The message you send with trigger_error() must be a string. But if you wanted to send an array (or even an object) you can use serialize()

From the manual;

To make the serialized string into a PHP value again, use unserialize(). serialize() handles all types, except the resource-type. You can even serialize() arrays that contain references to itself. References inside the array/object you are serialize()ing will also be stored.

Note: In PHP 3, object properties will be serialized, but methods are lost. PHP 4 removes that limitation and restores both properties and methods. Please see the Serializing Objects section of Classes and Objects for more information.

So for a simple example using an array;


<?php
// Some demo class
class MyEvent {
    function MyEvent () {
        echo ('Hello World!');
    }
}

// Override the E_USER_NOTICE constant with our own name
define ("EVENT",E_USER_NOTICE);

// Set what errors we will respond to, including EVENT
// Note that this overrides PHP ini
error_reporting  (E_ERROR | E_WARNING | E_PARSE | EVENT);

// Define the error handling function
function myErrorHandler ($errno, $errstr, $errfile, $errline) {
    switch ($errno) {
        case EVENT:
//            $event=& new $errstr;

            // Convert the string back to it's original form
            $errstr=unserialize($errstr);
            echo ( "<pre>" );
            print_r ($errstr);
            echo ( "</pre>" );
            break;
        default:
            die ("Application Error:[$errno] $errstr<br />\
");
            break;
    }
}

// Tell PHP to use the error handler, overriding the in
// built handler - all errors go by our function
$eHandler=set_error_handler('myErrorHandler');

$array=array("one","two");
// turn the array into a serialized string
$array=serialize ($array);

// Trigger an event
trigger_error ($array, EVENT);
?>

It getā€™s interestingā€¦

Looks like the error handler itself must be a function - had a quick go at defining a class, even using ā€˜myErrorHandler::myErrorHandlerā€™ without success.

OK - going for the triple post, hereā€™s one way this could be used;


<?php
/* Event Trigger Script
 * Usage:
 * Include this file in all your scripts
 * You can then fire off an event from anywhere using
 *
 * triggerEvent ($Object,$nameOfObjectMethodtoExecute,$params);
 *
 */

/* Override the E_USER_NOTICE with EVENT */
define ('EVENT',E_USER_NOTICE);

/* Set PHP's error reporting level */
error_reporting  (E_ERROR | E_WARNING | E_PARSE | EVENT);

/* Define the event trigger "error" handling function */
function eventTrigger ($eventType,& $event, $errfile, $errline) {
    switch ($eventType) {
        case EVENT:
            $event=unserialize($event);
            $event->object->{$event->execute}($event->params);
            break;
        default:
            die ("[$eventType] Error in $errfile on $errline: $event<br />\
");
            break;
    }
}

/* Tell PHP to use the eventTrigger handler
 * This overrides PHP's normal error handling
 */
$eventTrigger=set_error_handler('eventTrigger');

/* Use this function to fire off events */
function triggerEvent (& $object,$execute,$params=null) {
    $event=new stdClass;
    $event->object=$object;
    $event->execute=$execute;
    $event->params=$params;
    $event=serialize($event);
    trigger_error ($event, EVENT);
}
?>


<?
// Include the above script
require_once('eventTrigger.php');

class MyEvent {
    var $msg;
    function MyEvent () {
        $this->msg='Hello ';
    }
    function display ($name) {
        echo ($this->msg.$name);
    }
}

$myEvent=& new MyEvent();

triggerEvent($myEvent,'display','Harry');
?>

Thinking hard about this, Iā€™m not sure if weā€™ve actually gained anything here - every time I think too hard about this stuff, brain melts down.

May be someone else can work out if this is useful.

Wait a second - think weā€™re getting there (sorry - fourth in a row but canā€™t help myself);

Using the eventTrigger script;


require_once('eventTrigger.php');

class MessageOne {
    var $msg;
    function MessageOne () {
        $this->msg='Hello ';
    }
    function display ($name) {
        echo ($this->msg.$name."<br />");
    }
}

class MessageTwo {
    var $msg;
    function MessageTwo () {
        $this->msg="See you on ";
    }

    function output ($day) {
        echo ( $this->msg.$day."<br />");
    }
}

// Here's a list of events like Vincents up there

$events=array (
    'name'=>array ('class'=>'MessageOne','accessor'=>'display'),
    'appointment'=>array ('class'=>'MessageTwo','accessor'=>'output')
    );

// I use $_GET here for simplicity - this could
// be Phils classes for an alternative example

foreach ($_GET as $event => $param) {
    if (array_key_exists($event,$events)) {
        $thisEvent=& new $events[$event]['class'];
        $thisExecute=$events[$event]['accessor'];
        triggerEvent($thisEvent,$thisExecute,$param);
    }
}
?>

Here, the events will happen in the order theyā€™re given to the server, e.g.

  1. http://localhost/event.php

ā€¦ Displays nothing

  1. http://localhost/event.php?name=Harry

Displays:


Hello Harry

  1. http://localhost/event.php?name=Harry&appointment=Tuesday

Hello Harry
See you on Tuesday

  1. http://localhost/event.php?appointment=Tuesday&name=Harry

See you on Tuesday
Hello Harry

Now this doesnā€™t incorporate Vincents last suggests right now, but I think some tuning of the event ā€œtablesā€ (the above $events variable) could get that right.

The only further thing that might be worth modifying is being able to pass multiple method names to execute, in an array for example, to the error Handling function.

I like this because the code is minimal, fairly generic and simple to use.

Go hard or go home :slight_smile:

What happens when your script actually throws an E_USER_NOTICE?

I still donā€™t like the idea of b a s t a r dizing the trigger_error() function that way.

Couldnā€™t you just write a function that behaved like trigger_error() but didnā€™t actually handle errors?

E_USER_NOTICE is only generated by trigger_error() - i.e. only the code we write generates this. The same goes for E_USER_WARNING and E_USER_ERROR.

So thereā€™s a trade off in that we lose that error level to use in conjunction with trigger_error(). But in a way, this is the purpose of these three error levels.

Say, for instance, you ran a giant forum and wanted to warn your users to log off, because youā€™re about to do maintenance - perhaps youā€™d use trigger_error() with E_USER_NOTICE to make everyone see a message ā€œSite will be down in 15 minutesā€. Course thereā€™s other ways to achieve the same but that could work out quite elegant.

In this case, weā€™re turning the purpose upside down - rather that using the error level to notify the user, weā€™re using it so the user can tell the application what to do.

And if we really want to have loads of custom error messages, we could simply use E_USER_WARNING like,


trigger_error("Code 54362: That number is too big", E_USER_WARNING);

Whatā€™s good about trigger_error, is it already has an API, itā€™s fast (compared to some classes we write) and is guaranteed to work from anywhere.

Some other interesting things been coming across. Seems there is a way to specify a class method, using a workaround, as the default error handler.

Also, on a related subject, using output buffering, itā€™s possible to buffer output in stages, either be ā€œnestingā€ buffers within each other, or repeatedly writing to, caching then flushing the buffer.

This could be used to assign the output of a class to the buffer then store it in a file with an expiry date. Next time that class is going to be used, you check to see if thereā€™s a cached version which hasnā€™t expired first and if so, use that instead of the class. That means for a database query where the results only change once a week say, you could have it an expiry of that long.

Anyway - all interesting stuff.

OK - about to go on holiday so not much time. Attached is a ZIP with some classes and a demo that shows event handling using PHPā€™s errors. It need more work of course - the real error handling doesnā€™t work correctly yet. Also want to make use of output buffering to allow for dynamic caching of output. Finally Iā€™m only working with $_GET right now.

But it shows the events concept.

To configure it, everything is done in config.php. ā€œHandlersā€ go in the handlers directory.

The overall idea of this code is you place all your own code inside the ā€œSiteContainerā€. You use handlers to fire off your own stuff (be they objects, procedural scripts or whatever).

There are two types of event Iā€™ve defined;

Static Events - these should always be run no matter what incoming requests there are from a user. Things like the PageHeader, sessions, security etc. Also see the Form handlers.

Dynamic Events - these are only run when a user does ā€œsomethingā€, such as entering their name.

The way events are processed is kind of 3D;

In config.php youā€™ll see;

$staticHandlers[0]
$staticHandlers[1]
$dynamicHandlers[0]

First to be processed is anything in $staticHandlers[0]

Then $dynamicHandlers[0] is examined and events registered there are processed in the order which the events were sent.

Then $staticHandlers[1] is processed (and after that $dynamicHandlers[1] if it existed).

Anyway - makes an interesting toy.

Over the past few weeks Iā€™ve been thinking a lot about how to make a generic event handling mechanism. Iā€™ve also re-read this thread and Iā€™d like to make a few comments on some of the things discussed.

Originally posted by HarryF
Been thinking about the problem of how deal with multiple events. My opinion is the best way is to handle them in the ordered they are gathered from various sources (my first attempt dealt with them in the order they were registered - in other words how they appeared in Vincents $events array above). So if we use something like phils classes up there, perhaps in a single class called EventReceiver, extending Vincents example it might be

I think you are mixing things up a bit. An application can only respond to a single event at a time, you can not send more than one event to the application simultaneously.

If I understand you correctly, you are probably thinking about a situation like this. The user requests a page for adding a news item. The user must be logged in to access this page. So the script would have to execute an event/command for the login cookie and one for the add news page.

When in fact there is only one ā€˜eventā€™ that is executed here: the add news page. How this event is executed depends on whether there is a valid login cookie present. If that cookie is not present, the event should fire up a login page, otherwise it should just present the news page to the user.

Originally posted by voostind
The second case is, I think, much more common: an event is triggered, which in turn triggers another event. A user selects the page ā€˜documentationā€™, and on that page he selects the ā€˜introductionā€™ section. The ā€˜sectionā€™ event table makes no sense for any other page than the one with the documentation. This case can easily be solved by using an EventTable inside a triggered event. Events can be nested infinitely, as long as they arenā€™t based on the same name.

Same here. Since the web is - by nature - stateless, any event or request is standalone. Of course we use things like sessions to maintain state, but in essence it is still stateless.

So, continuing voostindā€™s example: a user selects the documentation page and the introduction page. Because of this stateless nature of the web, these requests can be made in any order. A user could for example first access the introduction page from a bookmark and then click on a link to the documentation page. Maybe Iā€™m completely missing the point, but I think that supporting ā€˜multiple eventsā€™ in a web application is a bit pointless, because they are in fact only single events.

Iā€™d like to second that, unless someone can convince me otherwise.

Attached is the current version of my Event Handling class, with an ( extreamly basic ) example.

Vincentā€™s post reminded me of stuff I read about Struts, a java opensource framework.

I think some of you might be interested in reading the users guide, especially :

http://jakarta.apache.org/struts/userGuide/building_controller.html

If I understand it correctly, struts has an XML file where all ā€˜eventsā€™ (aka Commands / Actions) are registered. When a request is received, struts looks in this XML file to determine the appropriate Action class to instanciate and to execute.

I donā€™t like the fact however that you seem to be tied to registering each form and its field in that XML file too.

This is explained at :

http://jakarta.apache.org/struts/userGuide/building_controller.html#action_mapping_example

One of the advantages is to let you change the application flow without editing the code (but the configuration file). I guess a side effect is that the file can be seen as a (partial) description of the dynamic parts of the application.

vudu - thanks for the tip off on struts - youā€™re spot on! When I get some more time to spend on this, Iā€™ll update the code to do things that way (I like it when thereā€™s already some kind of standard in place).

And reading that struts description, thatā€™s exactly what Iā€™m aiming for - what Iā€™m actually heading for is not really event handling but the ā€œControllerā€ in a Model View Controller pattern.

The goal of an Action class is to process a request, via its execute() method, and return an ActionForward object that identifies where control should be forwarded (e.g. a JSP) to provide the appropriate response.

Many thanks again for a push in the right direction.

The Chain of Responsibility pattern (Gang of four) has a different approach to this problem
(in case hashtables donā€™t satisfy your wants/needs)

The method of event-handling Chain of Responsibility describes is more suited to desktop applications, where an event is something like the user clicking on the screen.

I think that in this thread we have established that in PHP an event is a request made by the browser to an application. Correct me if Iā€™m wrong, but I donā€™t see how CoR could be helpful in such a situation, especially because CoR works from ā€˜smallā€™ (in a desktop application: something like a textbox) to ā€˜bigā€™ (the entire screen) and event handling in PHP works exactly the opposite way: from ā€˜bigā€™ (the entire application, a module) to ā€˜smallā€™ (a specific command to execute).

Ran into an interesting PHP project today: http://phrame.itsd.ttu.edu/

Phrame is a Web development platform for PHP that is based on the design of Jakarta Struts. It provides a basic Model-View- Controller architecture, and adds standard components such as HashMap, ArrayList, and Stack.

Open Source

As I understand, events in php are used to organize the flow of execution on top level of program. I see the problems with huge switch statement or chain of ifs (have had problems with this myself) but I still donā€™t see the need for so sophisticated solutions as those presented in this thread. Here is what I invented to cure the ā€œchain of ifsā€ problem.

Usually my scripts (exept those that contain only function or class definitions) had three parts:

  1. first I set things up like connect to database, create helper objects and so on

  2. then I had that chain of ifs, to decide what action to take this time. If there was no action needed, then this part did nothing.

  3. and then I showed the page

After reading this thread I tried to encapsulate responsibilities of a script into an object like this:

In every script there is an object that encapsulates all functionality that user can get out of this script. Lets call it ā€œa moduleā€. Its task is to provide interface for functionality not to implement it.


class EmployeeModule extends WebModule
{
	// 1. - constructor
	// don't care whether there will be an action this time
	function EmployeeModule ($params)
	{
		$this->db = new DBConnection ('scott', 'tiger');
		// any other setup here, including creating
		// business objects
	}
	
	// 2. - public "action" methods or event handlers.
	// What method to call depends on a special input
	// parameter, for example 'task' or 'message', all
	// other parameters end up as parameters for that method
	function update ($params)
	{
	}
	function delete ($params)
	{
	}
	function doSomethingElse ($params)
	{
	}
	
	// 3. - presentation
	// don't care whether there was an action this time
	function show ($params)
	{
		// here compile all data to present and then ...
		include ('Employee.template.php');
	}
}

so, I deciced to use the set of public methods instead of events table.
As each method takes one parameter (an associative array of script input parameters), itā€™s possible to manipulate this object automatically like this:


// creates module; if there is an action parameter present
// then calls module's handleMessage($msg, $params) method
// (this is inherited from WebModule and by default calls
// method named $msg)
// and then asks module to show itself
RequestHandler::run ('EmployeeModule');

Module donā€™t depend on $_GET or something like this, so its possible to use it as a submodule in another module that may forward some of its messages (or events) to submodules.

Or you can use a module like a switch that takes care of application level tasks like user authentication and database connection but forwards specific messages to a module specified with an input parameter.

Main classes are very simple:


class WebModule
{
	function WebModule ($params=array())
	{
		// do nothing by default
	}
	
	function handleMessage ($msg, $params=array())
	{
		return $this->$msg($params);
	}
	
	function show ($params=array())
	{
		// output nothing by default
	}
}



class RequestHandler
{
	function run ($module_class_name)
	{
		$params = array_merge ($GLOBALS['HTTP_GET_VARS'],
			$GLOBALS['HTTP_POST_VARS']);
		
		$module = new $module_class_name ($params);
		
		if ($params['_msg']) {
			$module->handleMessage ($params['_msg'], $params);
		}
		
		$module->show($params);
	}
}


Iā€™d be happy hearing your critics on this idea.

Aivar

Correct me if Iā€™m wrong, but I donā€™t see how CoR could be helpful in such a situation, especially because CoR works from ā€˜smallā€™ (in a desktop application: something like a textbox) to ā€˜bigā€™ (the entire screen) and event handling in PHP works exactly the opposite way: from ā€˜bigā€™ (the entire application, a module) to ā€˜smallā€™ (a specific command to execute).

Sure, CoR could be a bit ā€˜too muchā€™, but then, I think that entirely depends on how you apply the pattern. If your interpretion of ā€œspecific commands to executeā€ were represented as handler classes that take care of the desired action to be taken, itā€™s not all that hard to build a chain of responsibility, and in some cases you could probably even benefit from it (Iā€™m just assuming you know how the pattern can be applied).
But like I said, applying CoF in situations described here is usually way beyond what you want, but Iā€™m just saying it can be done that way. I personally use a hashtable in most of these cases, easy enough. Iā€™m not even a fan of CoF, but thatā€™s a completely different story :slight_smile:

this is a bit off topic, but how are events on pages like these are handled, iā€™m not too sure what their backend are driven by, but how they work with ā€œ,ā€.