Event Handling Classes in PHP

Harry,
I understand that what you called event I call command.
That’s because I’m implenting these concepts:
http://www.martinfowler.com/isa/frontController.html

Your works seems nice,
but I want to point something.

I see from your code that you use often:
$_REQUEST[$eventname]

Firs of all,
why not use a class HttpRequest instead ?!?
Is it not better to use something like
$request->getParam( $eventname );
instead of
$_REQUEST[$eventname]
?!?
Your scripts will be more simple to mantain.

Second thing,
using $_REQUEST[$eventname] you are assuming that “events” are passed via a _POST or a _GET or whatever.
But events could be passed to application by _REQUEST_URI
using something like:
/somepar1/someval1/somepar2/someval2
( Okay here I could put some redirection rules ).
Doing $_REQUEST[$eventname] I will have to rewrite the application on many parts.

Would not be better to have a class that acts as an interface to the http requests and a class that acts as an event manager ?!?
So the aim of the HttpRequest class is an interface to the http protocol requests.
So I will read parameters as ->getParam(xx); and not as _GET[xx].
The “event manager”, or whatever name you want to give it, will transform http requests ( that could be via GET, or via REQUEST_URI ) to events.

I admit that I didn’t read your code because I have not too much time available,
so I’m sorry If I’m saying something wrong.

I wanted only to share some thoughts.

:slight_smile:
pippo

Originally posted by Captain Proton
Alright here we go again… What is missing in PHP’s OO support according to you?

But we’re going way off topic, so if you want to continue the PHP OO discussion feel free to start a discussion in a new thread or something.

Since – to date – you’ve made 85 posts, I’m going to assume you haven’t been around for the endless number of “PHP isn’t OO” threads that go on around here. I’ve recently made my 800th post, and I haven’t even scratched the surface of how common some topics are here.

I won’t be drawn into that argument here and now. Ignore my statements about OO support in PHP.

Pippo - you’re spot on and thanks for that link.

The use of $_REQUEST is bad - my first guess at how to solve the problem with PHP.

I won’t be drawn into that argument here and now. Ignore my statements about OO support in PHP.

Yeah good plan. Let’s save that for the other 3 million threads that deal with that intriguing topic :slight_smile:

And i agree that link pippo gave is cool.

I think the java architecture has a good framework for this sort of thing.
I’ve read that the architecture has good functionality like that, but because it generates so much overhead it slows the execution down. I dunno about that cause i don’t know enough…

I created just few days ago this class as interface to the http requests.


<?php

/*
 ** Class Name : HttpRequest
 ** Description: This class is the interface to all the requests of
 **              the http protocol.
*/
class HttpRequest
{
	/*
	 ** Function Name: HttpRequest
	 ** Parameters   : none
	 ** Return       : none
	 ** Description  : Class constructor.
	*/
	function HttpRequest()
	{
	}

	/*
	 ** Function Name: getMethod
	 ** Parameters   : none
	 ** Return       : string - method name
	 ** Description  : Return the method name of the http request.
	*/
	function getMethod()
	{
		return isset( $_SERVER[ 'REQUEST_METHOD' ] ) ?
			$_SERVER[ 'REQUEST_METHOD' ] : false;
	}

	/*
	 ** Function Name: getParam
	 ** Parameters   : $name - parameter name
	 ** Return       : parameter value
	 ** Description  : Get the value of the parameter.
	*/
	function getParam( $name )
	{
		switch ( $this->getMethod() )
		{
			case 'GET':
				return isset( $_GET[ $name ] ) ? $_GET[ $name ] : false;

			case 'POST':
				return isset( $_POST[ $name ] ) ? $_POST[ $name ] : false;
		}

		return false;
	}

	/*
	 ** Function Name: getRequestUri
	 ** Parameters   : none
	 ** Return       : request uri
	 ** Description  : Get the request uri.
	*/
	function getRequestUri()
	{
		return isset( $_SERVER[ 'REQUEST_URI' ] ) ?
			$_SERVER[ 'REQUEST_URI' ] : false;
	}
}

?>

It’s far from being complete ( no cookies, etc ) and far from being an example for people,
but that was my approach to have an object to interface to the http protocol.
I was trying to be inspired from what Java did.

If you guys find that class interesting ( nothing fantascientific ) and you would want to expand it I would be happy :).

:slight_smile:
pippo

In fairness to Harry, I wanted to provide some code to show that I’m not just a critical bystander.

<?php
class EventMarshall
{
	//Constructor
	function EventMarshall()
	{
		if($_GET)  $this->trigger_get();
		if($_POST) $this->trigger_post();
		
		if($_SERVER['PATH_INFO']) $this->trigger_path_info();
		
		// default page trigger
		if(function_exists('main')){
			main( &$this );
		}
	}
	function trigger_get()
	{
		// GET trigger
		if(function_exists('on_get_value')){
			reset($_GET);
			while(list($key,$value)=each($_GET)){
				on_get_value( &$this, $key, $value );
			}
		}
		if(function_exists('on_get')){
			on_get(&$this, $_GET);
		}
	}
	function trigger_post()
	{
		// POST trigger
		if(function_exists('on_post_value')){
			reset($_POST);
			while(list($key,$value)=each($_POST)){
				on_post_value(&$this, $key, $value);
			}
		}
		if(function_exists('on_post')){
			on_post(&$this, $_POST);
		}
	}
	function trigger_path_info()
	{
		// PATH_INFO trigger
		$data = explode('/', $_SERVER['PATH_INFO']);
		if(function_exists('on_path_data')){
			for($i=0;$i<count($data);$i++){
				if($data[$i])
					on_path_data(&$this, $data[$i]);
			}
		}
	}
}
new EventMarshall();
?>

which could be implemented in the following manner…

<?php

require_once 'EventMarshall';

function on_path_data( $sender, $value ){
	switch($value){
		case 'foo':
			print '<b>foo</b> command issued.<br/>';
			break;
		default:
			printf('unrecognized command "%s" issued.<br/>', $value);
			break;
	}
}
function on_get_value( $sender, $key, $value ){
	printf('<b>%s</b> = %s<br/>', $key, $value);
}
function main( $sender ){
	print 'hello world.<br/>';
}

?>

randem i like how you have extended pippo’s idea so that if you make a POST request and pass GET variables they can still trigger events.

bit i think that the code posted just delay’s the fusebox approach to ‘events’ even further…

I mean you still have to have a big switch statement or parse what is requested and then do something.

I think that it is a good idea to be able to call:
[PHP
$request->getParam(“param”)



and for that parameter to be returned whether it is a GET or POST request, but i like Harry's idea that you can register a param or event with a handler and that handler can automatically be called.

I think that if you have one generic handler like:

```php

   function on_get_value( $sender, $key, $value ){
     //parse $key then call specific handler for event.
   }

then you are just delaying the same action that would occur if you ran everything by the fusebox/index.php?action=blah approach.

—edit----

And i know i don’t offer any code… but i will when i finish my current uni assignment. Honest!!

Its possible to extend the idea of a http-request handler even further. For instance, a base class containing basic POST/GET/etc array handlers, and extension classes to handle query-string parsing and cookie handling. This way you could have a single application for standard query param oparsing, and one that could also interpret “search engine friendly” parsing.

Heres a basic CGI class I made not long ago, I’ll work on expanding it in the above fasion after work…

<?php
//
// +----------------------------------------------------------------------+
// | CGI                                  |
// +----------------------------------------------------------------------+
// | Copyright (c) Phil Roberts                      |
// +--------------------------------------------------------------------
// | Authors: Phil Roberts <phil@heropr.com>                |
// +----------------------------------------------------------------------+
//
// $Id: CGI.php,v 1.0 28/09/2002
//

class CGI
{
  // {{{ properties

  /**
  * Stores the query parameter array.
  * @var     array
  * @access  private
  */
  var $_param;

  /***
  * Stores the ampersand-parsing parameter.
  * @var    bool
  * @access private
  ***/
  var $_use_ampersands;

  /**
  * Default constructor
  */
  function CGI()
  {
    // Sets the default value for $this->use_ampersands

    $this->use_ampersands(1);

    if( strtolower($GLOBALS['REQUEST_METHOD']) == 'post' )
    {
      while( list($k,$v) = each($GLOBALS['HTTP_POST_VARS']) )
      {
        $this->_param[ $k ] = urldecode($v);
      }
    }
    else
    {
      if( $this->_use_ampersands == 1 )
      {
        while( list($k,$v) = each($GLOBALS['HTTP_GET_VARS']) )
        {
          $this->_param[ $k ] = urldecode( $v );
        }
      }
      else
      {
        $this->parse_params($GLOBALS['QUERY_STRING']);
      }
    }
  }

  /**
  * Returns the value of the parameter key $var.
  *
  * @return string
  * @access public
  */
  function param($var)
  {
    return $this->_param[ $var ];
  }

  /**
  * Parses a query string to an array.
  * This enables mixed use of both & and; as query
  * string delimeters.
  *
  * @return array
  * @access private
  */
  function parse_params($tosplit)
  {
    $pairs = preg_split('/[&;]/', $tosplit);

    foreach( $pairs as $_ )
    {
      list($k,$v) = explode('=', $_);

      $value = isset($value) ? $value : '';

      $param = urldecode($k);
      $value = urldecode($v);

      $this->_param[ $param ] = $value;
    }
  }

  /**
  * Returns an associative array of all query values.
  *
  * @return array
  * @access public
  */
  function all_parameters()
  {
    return $this->_param;
  }

  function use_ampersands()
  {
    $this->_use_ampersands = 1;
  }
}

?>

Hi,
talking about _REQUEST usage:

From manual:

An associative array consisting of the contents of $_GET, $_POST, $_COOKIE, and $_FILES.

http://www.php.net/manual/en/reserved.variables.php#reserved.variables.request

from a bug report:

The ‘voting’ on php-dev was in favour for removing the $_FILES from
$_REQUEST…as it doesn’t make much sense
to have them there. Just FYI. :slight_smile:

http://bugs.php.net/bug.php?id=19848

I thought interesting give this feedback.

:slight_smile:
pippo

Okay, heres what Ive come up with for my own HTTP Request handler library. It handles both & and ; delimeted query-strings and parses ‘search-engine friendly’ style url’l as well as handling cookies.

Its by no means a finished work, but I was curious as to what you all thought of it. :slight_smile:

Here is the base class, HTTP_Request:

<?php

class HTTP_Request
{
    var $param;
    var $raw_query;
    var $cookie_vars;
    var $query_string;

    function HTTP_Request()
    {
    }

    function parse_requests()
    {
        if( is_array($GLOBALS['_GET']) )
        {
            while( list($gk,$gv) = each($GLOBALS['_GET']) )
            {
                if( is_array($gk) )
                {
                    while( list($ngk,$ngv) = each($GLOBALS['_GET'][$gk]) )
                    {
                        $this->raw_query[$gk][$ngk] = $ngv;
                    }
                }
                else
                {
                    $this->raw_query[$gk] = $gv;
                }
            }
        }

        if( is_array($GLOBALS['_POST']) )
        {
            while( list($pk,$pv) = each($GLOBALS['_POST']) )
            {
                if( is_array($pk) )
                {
                    while( list($npk,$npv) = each($GLOBALS['_POST'][$pk]) )
                    {
                        $this->raw_query[$pk][$npk] = $npv;
                    }
                }
                else
                {
                    $this->raw_query[$pk] = $pv;
                }
            }
        }

        if(is_array($_COOKIE))
        {
            while( list($ck,$cv) = each($_COOKIE) )
            {
                if(is_array($ck))
                {
                    while( list($nck,$ncv) = each($_COOKIE[$ck]) )
                    {
                        $this->cookie_vars[$ck][$nck] = $ncv;
                    }
                }
                else
                {
                    $this->cookie_vars[$ck] = $cv;
                }
            }
        }

        if(isset($_SERVER['QUERY_STRING']))
        {
            $this->query_string = $_SERVER['QUERY_STRING'];
        }
    }

    function get_Requests()
    {
        return $this->raw_param;
    }

    function get_Cookies()
    {
        return $this->cookie_vars;
    }

    function set_Cookie($name,$value='', $expire='',$path='',$domain='',$secure='')
    {
        $expire = $expire == "" ? "" : time()+3600;

        return setcookie($name,$value,$expire,$path,$domain,$secure);
    }

    function param($pval)
    {
        die('Feature not implemented.');
    }

    function dump_Params()
    {
        die('Feature not implemented.');
    }
}

?>

The query-string parser class:

<?php

if(!defined('CLASS_ROOT'))
{
    define('CLASS_ROOT', '');
}

include_once CLASS_ROOT."HTTP_Request.php";

class QS_HTTP_Request extends HTTP_Request
{
    var $use_ampersands;

    function QS_HTTP_Request($use_amp = 'false' )
    {
        $this->use_ampersands = $use_amp;

        $this->parse_requests();

        if(is_array($this->raw_query))
        {
            if( $this->use_ampersands == 'false' )
            {
                while( list($k,$v) = each($this->raw_query) )
                {
                    $this->param[ $k ] = urldecode($v);
                }
            }
            else
            {
                $this->parse_params($this->query_string);
            }
        }
    }

    function param($pval)
    {
        return $this->param[$pval];
    }

    function dump_Params()
    {
        return $this->param;
    }

    function parse_params($tosplit)
    {
        $pairs = preg_split( '/[&;]/', $tosplit );

        foreach( $pairs as $vars )
        {
            list($k,$v) = explode( '=', $vars );

            $value = isset($value) ? $value : '';

            $param = urldecode($k);
            $value = urldecode($v);

            $this->param[ $param ] = $value;
        }
    }
}

?>

And the search-engine friendly url parser class:

<?php

if(!defined('CLASS_ROOT'))
{
    define('CLASS_ROOT', '');
}

include_once CLASS_ROOT."HTTP_Request.php";

class SEF_HTTP_Request extends HTTP_Request
{
    var $temp_vars;

    function SEF_HTTP_Request()
    {
        $this->parse_requests();

        $this->temp_vars = explode( '/',$_SERVER["PATH_INFO"] );

        array_shift($this->temp_vars);
        reset($this->temp_vars);

        while( list($v,$k) = each($this->temp_vars) )
        {
            list($index,$val) = each($this->temp_vars);
            $val = urldecode($val);
            $this->param[$k] = $val;
            $$k = $val;
        }
    }

    function param($pval)
    {
        return $this->param[$pval];
    }

    function dump_Params()
    {
        return $this->param;
    }
}

?>

The thing i like most about Harry’s code is that he uses the abstract concept of events.

a CGI or Servlet-like mechinism is very similar in the internals, but the way that Harry has structured the registering of events makes it very easy to abstract the workings of an application.

Say you want to sit down and try to design an application, then the first part you want to do is identify any patterns that can be used and and design an abstract application architecture. You don’t want specifics such as particular request types or variables… you want to identify the events or requests as a whole and defer the implementation specifics.

Being able to parse any sort of application request is terrific, but the application is not going to implemented the best unless you are able to define the process it does in abstract terms. The use of events could be a terrific way to do that.

You also don’t want to tie an event to a specific set of protocols such as HTTP requests. What if, in the future requests are made via XML-RPC?

XML-RPC might use HTTP or it might not, and you might want to later integrate your application with a J2EE application or .NET application.

If you can keep the event registering/triggering abstract enough then when new ways of doing things or different platforms are integrated, then you’ll save yourself a hell of of alot of re-coding or messing work arounds.

The idea of event handling / registering is very interesting and if its done properly, I think a lot of people would be interested in using it (count me in :)).

To keep things as abstract as possible, I think you need a couple of layers for event handling. This is just what I could come up with right now, it’s in no way complete.

  • Event Receiving
    Can receive events from HTTP GET, POST, possibly REQUEST_URI (using something like /news/archives/2002/10), XML-RPC, through the command line possibly. You could implement subclasses of an abstract base class for these. This class would give an event to the layer below it:
  • Event Dispatching
    These would look up events in some kind of registry and load the appropriate Event Handler class to further handle the event.

    PHP scripts are compiled and loaded everytime a request is made, and this of course takes processing time. Therefore I think it would be a good idea to dynamically load the Event Handlers. So the registry would also need a file name to include() for the event.
  • Event Handling
    Subclasses of an abstract subclass or something that handle events. Theoretically of course, it shouldn’t matter if the event comes via GET, POST, XML-RPC or whatever. However I think the way the event is handled usually depends on the way it is received
  • Domain / Business Logic
    In a correctly layered system (I’m following http://www.martinfowler.com/isa ), the Events (or Commands as Fowler calls them, I think) shouldn’t do the actual work themselves but should delegate it to the Domain layer.

That’s just my 2 cents :slight_smile: Looking forward to feedback

I have been following this thread with interest, and now that the discussion has seemed to have skidded to a halt, I’d like to post some of my observations here. Please correct me if I’m wrong.

Harry started this thread about event handling because he didn’t like the idea of writing long and complicated switch-statements in a single PHP-script to handle all possible events. This, I completely agree with; I don’t like them either.

The solution to Harry’s problem is - as it seems now - a couple of classes that can handle the registering and execution of various events. With these classes, a programmer can define a number of events for some page, after which the code connected to the events will take care of handling them. Bye bye switch-statement… Or not?

In theory, the switch-statement is still there. But instead of using long and boring conditionals, the idea is as follows:

  • Build a table of possible execution paths, indexed on a simple name (the event)
  • Find out which event was triggered
  • Lookup the selected event in the table
  • Execute the code specified in the table for that event

This is a sound solution, because it separates the algorithm (event selection) from the task at hand (executing events). This allows the first to be reused and parametrized for other problems.

In short, I think this sums up what has been discussed in this thread.

Now, I have a serious problem with the code presented in this discussion. Simply put, I think there’s too much of it. Don’t get me wrong, I don’t disagree with Martin Fowler at all (I wouldn’t dare :)), but I do think a much simpler implementation is possible, and more importantly, needed. How do you explain to a procedural programmer that he shouldn’t write a large and complex switch-statement to handle events, but should instead use about 4 or 5 classes instead? These classes alone are at least twice as big as the whole switch-statement (which, in the end, implements the exact same functionality)!

All in all, the code in this thread made me think a bit of the template engine discussion we had earlier. Indirection after indirection is introduced, but in the end each indirection - a solution to a partial problem - has little effect on the problem as a whole. The result is that we end up with a large pipeline of code blocks executed one after another, each doing very little.

So what can we do about it? I think that by going back to the problem we’re actually trying to solve we can come up with a much simpler solution that could convince a procedural programmer to use OO. A solution that is implemented with little code, is easy to use and read, and introduces little overhead.

Instead of using a complex switch-statement, we’re going to use an event table. A client calls ‘index.php?page=home’, and we translate this request by looking up ‘home’ in the event table that generates pages. As such, the input-side of the problem is pretty simple: it’s just a string. The output-side is a bit harder: code specific for some event needs to be executed. By using OOP, this can easily be solved: implement a baseclass (e.g. ‘Event’) with a single method (‘execute’). Each event is then a subclass of this class, executing specific code. An event table can thus be implemented as a single array:


$events = array(
    'home'     => new StaticPage('html/home.html'),
    'news'     => new NewsPage('news/*.html'),
    'feedback' => new StaticPage('html/feedback.html')
);

$event =& $events[$_GET['page']];
$event->execute();

And presto, there’s your event handling system, in just three statements…

Of course, there is a serious problem with this solution. The event table is populated with objects (class instances), of which only one is actually used. Thus, there’s a lot of overhead.

To solve that problem, we can use the Factory Method design pattern. Instead of instantiating an event object, we describe how an event should be created, after which we let a factory create an object for us based on this description:


$events = array(
    'home'     => array('class' => 'StaticPage', 'parameters' => 'html/home.html'),
    'news'     => array('class' => 'NewsPage'  , 'parameters' => 'news/*.html'),
    'feedback' => array('class' => 'StaticPage', 'parameters' => 'html/feedback.html')
);

$factory =& new EventFactory();
$event   =& $factory->createEvent($events[$_GET['page']]);
$event->execute();

The factory is just another indirection, but it’s a useful one, because it eliminates the overhead incurred earlier. Now, only a single object is actually created, namely the one that represents the event that will be executed.

There are still various problems with this simple solution. For example, an event class can only accept a single argument in its constructor. Also, errors aren’t handled properly. Personally, I don’t like the idea of specifying the event table in code. Why not write one in a separate file, and let the rest of the code use it automatically? For example:


key      | event      | parameters
home     | StaticPage | page     = 'html/home.html'
news     | NewsPage   | filemask = 'news/*.html'
feedback | StaticPage | page     = 'html/feedback.html'

Registering an event is now a simple matter of adding a line to the event table. There’s need to edit any code at all.

Of course, the code I presented here is in no way as powerful as the combined code posted by everybody else in this thread. Although the missing functionality can be added pretty easily without adding too many lines of code, that’s not the point of this post. My point is that I think we should take care not to ‘overdesign’ our solutions. Every problem should be solved in a simply a manner as possible. Adding indirection after indirection may result in a design that looks good on paper, but does it actually work better? I don’t think so. Simple is best, is my opinion…

Vincent

I’ve been abit busy, as I’m sure we all have.
Here is a simple event ‘table’ or ‘registry’:


<?
	if( !defined( 'ERR_LVL' ) ) {
		define( 'ERR_LVL', E_USER_WARNING );
	}
	
	class Event {
		function raise( $params ) {
			return( '\
<b>Unhandled Event</b><br />\
' );
		}
	}
	
	class EventHandler {
		var $events = array();
		
		function geteventnames() {
			return( array_keys( $this->events ) );
		}
		
		function addevent( $name, &$object ) {
			if( !is_object( $object ) ) {
				trigger_error( '<b>EventHandler Error:</b> Method <i>addevent()</i>, second parameter is not an object', ERR_LVL );
				return( false );
			}
			if( !is_subclass_of( $object, 'Event' ) ) {
				trigger_error( '<b>EventHandler Error:</b> <i>' . get_class( $object ) . '</i> is not derived from <i>Event</i>', ERR_LVL );
				return( false );
			}
			
			$this->events[$name][] = &$object;
			return( true );
		}
		
		function callevents( $name, $params = null ) {
			if( is_array( $this->events[$name] ) ){
				$num = count( $this->events[$name] );
				for( $idx = 0; $idx < $num; $idx++ ) {
					$value .= $this->events[$name][$idx]->raise( $params );
				}
			} else {
				$value = Event::raise( null );
			}
			return( $value );
		}
	}
?>

It’s very simple, and straight forward…
this is also setup to handle more then one type of event, and more then one event per type.

It can also be used as a standalone class, or part of a composition.

I’m in the middle of something at the moment, I’ll give an example of how you can use it later, if one is needed.

Think you’re completely right Vincent. And looking at your design, reckon it could scale nicely from a simple, but limited solution to a more complicated but flexible solution, as required.

One further problem I see if Vincents example is you’re tied to the $_GET variable $page.

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;


<?php
// This could be in a file - XML even
$eventRegister = array(
    'email'    => array('handler' => 'EmailHandler',
                        'widgets' => array (
                                            'FormControl',
                                            'FormResponse',
                                            )),
    'login'    => array('handler' => 'LoginHandler',
                        'widgets' => array (
                                            'FormControl',
                                            'FormResponse',
                                            )),
    'news'     => array('handler' => 'NewsHandler'  ,
                        'widjets' => array (
                                            'TableControl'
                                            )),
    'home'     => array('handler' => 'DefaultHandler',
                        'widgets' => array (
                                            'StaticControl'
                                            )),
    'feedback' => array('handler' => 'FeedbackHandler',
                        'widgets' => array (
                                            'StaticControl'
                                            ))
);

// Get all the incoming events
$receiver=& new EventReceiver();
$receivedEvents=$receiver->listEvents();

$factory =& new EventFactory();
foreach ($receivedEvents as $name => $values) {
    $event=& $factory->createEvent($events[$name],$values);
    $event->execute();
}

?>

Or something like that. Again I’m think not perfect - getting too complex…

If I read Martin Fowler right, he’s saying you have two choices - one is to use hardcoded conditional statements but keep the incoming data flexible (when you need a new $_GET variable, for example, you “just” code the procedural logic to deal with it) or, to use classes to deal with handling, you have to define a fixed “namespace” which incoming variables have to be received on, e.g. something like;

http://www.domain.com/{packagename}/{classname}/{methodname}/{parameter_1}/.../{parameter_n}

Anyway - still thinking.

Looking at you code Vincent - great way to do things - think I can see where you’re going with that.

One other idea - what about using custom error handlers and trigger_error to “invoke” events? Just a thought (looking at AcidReigns code)

Harry: can you elaborate on that?

Originally posted by HarryF
One other idea - what about using custom error handlers and trigger_error to “invoke” events? Just a thought (looking at AcidReigns code)

I think that would be pretty sloppy unless the event was actually a response to an error.

One further problem I see if Vincents example is you’re tied to the $_GET variable $page.

Yes, but that’s because my example code was bare code. If I put it in a class, I would tie it to a variable passed to an object on construction. Imagine a class EventTable that works like this:


$handler =& new EventTable('pages.dat');
$event   =& $handler->getEvent($_GET['page']);
$event->execute();

This EventTable stores a single event table. In this particular case I load the table from a file and then request the correct event by passing $_GET[‘page’]. I could also implement the EventTable so that it loads tables from a database, or allows them to be created dynamically. The call to getEvent in this case receives the value of $_GET[‘page’], but it could of course come from anywhere.

Been thinking about the problem of how deal with multiple events

I’ve been thinking about them too. Generally, I think there are two different cases:

  1. Events having equal priority. These are on the same level.
  2. Events dependent on other events. These are in multiple levels.

In the first case, I would create multiple events, one for each event table. These can then be processed in some order. For example:


$pageTable =& new EventTable('pages.dat')
$userTable =& new EventTable('user.dat')
// ...
$pageEvent =& $pageTable->getEvent($_GET['page']);
$userEvent =& $userTable->getEvent($_GET['user']);
// ...
$userEvent->execute();
$pageEvent->execute();

I think using multiple tables makes sense, because the events are disjunct; they are based on different, non-linked variables (‘page’ and ‘user’ in this case).

Of course, you can write some code on top of the EventTables class that handles this case (builds an array of event tables and then processes them in some order), but I think this case almost never occurs…

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.

I think that in web applications few events actually co-exist with one another on the same lever (the first case). You might have an event table for the page and the user, but if the user triggers a ‘logout’ event, this could easily result in a redirect to another page, as he could otherwise see a page only available to users that are logged in. So in that case the page-event handler must be disabled. By nesting the page-handler inside the user-handler, this is easily solved.

Anyway, just because I can’t think of a good example of a web-application using multiple events at the same level, that doesn’t mean they don’t exist… :slight_smile:

One other idea - what about using custom error handlers and trigger_error to “invoke” events? Just a thought (looking at AcidReigns code)

When you’re using some kind of event handling system, two different things can go wrong:

  1. User errors: events are left unspecified, or have the wrong value (someone specifies a page not in the event table, for example). These should be handled gracefully, and not trigger real PHP errors. Use default values for missing ones, for example.
  2. Programmer errors: tables are defined incorrectly, or events are passed illegal (static) arguments. In large systems it can be a good idea to detect and report these errors to the programmer. On the other hand, these checks should not impose overhead on the application itself, as a correct application has correct event tables, and thus no such errors.

Vincent

Flying in the face of reason for a moment, here’s the scam with PHP’s error handling;


<?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;
            break;
        default:
            die ("Error in $errfile on $errline:[$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');

// Trigger an event
trigger_error ('MyEvent', EVENT);
?>

It may be possible to define a class instead of a function as the error handler - yet to experiment with that.

What this does mean is it becomes very easy to trigger an event from anywhere.