Using OOP to Handle Events

Using OOP to Handle Events

To implement effective event-driven solutions in your own PHP applications, it is best to take a step back and determine, from a high-level perspective, how these applications should behave. A more sophisticated approach to implementing events would allow you to more easily control their behavior exactly as you wish, should the need ever arise. Creating an object-oriented solution to event-driven programming is the answer.

Not only will using classes and objects help you maintain uncluttered code, it will also allow you to create an easily extensible architecture. Extensibility in application development is all the rage these days, so this has to be a good thing. There are plenty of other benefits, too, which we will explore in some detail later in the chapter. Additionally, considering an OO approach should alert you to the fact that there might well exist a design pattern that could fit the event-driven model quite well.

Let's build a picture of what is needed to implement proper event handling using an object-oriented approach. We can then see whether a pattern, or patterns, exist that will fit this model appropriately.

Designing an Event-Driven Solution

There are three main steps to take when designing any event-driven application.

The first is to determine how you will go about capturing the events the application will need to handle. This is difficult to discuss in an abstract manner, since your approach to capturing these events relies on exactly what type of events you're trying to capture.

Second, you will need a way of deciding how to go about handling the events captured. Depending on the specific application you are considering, there may be a number of questions worth posing at this early stage. For example, it's worth determining how many distinct sources will be sending events to your event handler. Also, you will need to decide whether the application will handle just one class of event or a number of different classes, and, if necessary, how handlers will effectively differentiate between them.

Finally, after you have determined the events applicable to your application and decided how they will be handled, you need to design the handlers necessary to carry out whatever tasks the application requires in response to each event. The types of responses an event could elicit are almost unlimited. Anything from directing a user to a new page, to updating a record, to firing more events all could be regarded as proper responses.

Effectively, we have just described the use of a reactor, or dispatcher class, to take an event and make sure that the correct handler deals with it. Let's explore this idea a little further.

In keeping with our initial example, let's assume that a user of our application will want to edit or view records of some form or another. We could expect a URL such as the following to be requested as a result of a user's input returned from a form:

   http://myserver/interface.php?event=edit 

It's fairly clear from the URL that the event being triggered is an edit event, whatever that may mean. By passing this event to the dispatcher class, that determines which handlers to call based on what is required from the application. After the dispatcher determines which handler to call (in this case, by checking the values associated with the $_GET or $_POST array keys), the actual processing of the request is done by the relevant handler, and not by the dispatcher.

The handlers themselves are an exercise in OOP in their own right. Indeed, one effective way to implement individual handlers in your application is to extend a generic parent handler class. Using inheritance makes implementing new handlers easy, since the functionality common to all event handlers can be kept hidden away inside the parent class. For example, code to establish database connections can be made available to subclasses of handler that may require access to such a connection.

To begin with, we can map the perceived course of actions for this example by drawing the following activity diagram (Figure 10-1).


Figure 10-1

This is a pretty simple representation, but for the sake of the example we needn't worry about the niceties behind the implementation. Admittedly, a virtually unlimited number of concerns could well be brought to bear here on the strength of this illustration; security, database, handler registration, and multiple event sources are just some of the valid considerations you might well need to take into account at this stage.

Having said that, you are now equipped with a basic model to work from, so go ahead and look at the class diagram derived from these perceived requirements (Figure 10-2).


Figure 10-2

There is no need to be too concerned about the code driving the user interface here, because choices concerning such matters are better made by the end-user, not the software architect. Concentrate instead on representing in your class diagram the dispatcher and handlers. This application provides just the bare bones in terms of functionality; we have concentrated instead on the event-driven paradigm and the advantages derived from using it.

Implementing the Solution

Recall from the class diagram that we are making use of an interface to implement each event handler. The use of an interface to enforce the existence of certain methods within each handler is crucial to our example and is good practice in general since it will standardize the way in which your handlers are built. This is important, as your application may grow to the stage at which third-party developers want to add functionality of their own. Having an interface will promote the "plug and play" aspect of your code or, more simply, make your code more extensible.

Aside from being extensible, there are several other advantages yet to be gained. After presenting the classes in this section, we will look at how easy it is to implement fine-grained security using our model. First, we'll look through the code class by class.

class.Dispatcher.php

To begin with, the code for the Dispatcher class looks something like this:

   <?php    require_once('class.Event_Handler.php');    require_once('class.Handler_View.php');    require_once('class.Handler_Edit.php');    class Dispatcher    {       private $handle;       function __construct($event_handle){          $this->handle = $event_handle;       }       function handle_the_event(){          $name = "handler_{$this->handle}";          if (class_exists("$name")){             $handler_obj = new $name($this->handle);             $response = $handler_obj->handled_event();             return $response;          }else{             echo "I can't handle this!";          }       }    }    ?> 

Crucially, the constructor is responsible for capturing the event handle and retaining it within the class, so that it can be used later to decide what action to take. The event handle may be needed as part of the event handler's processing, so we will pass this value on to any handlers upon which we may call. Of course, the handlers might need additional information, too.

As part of our application, when querying the database for existing records, the user might want to return only records in a certain range. When this crops up, you can simply capture the delimiter parameters as part of the event, as demonstrated in the following URL:

   http://myserver/interface.php?event=edit&first=2&last=7 

It is a simple enough job to populate a suitable array (for example, $event_parameters) as part of the constructor. These values can then be passed on to the handler, which will use them to return records within only the specified range. We'll stick to simply sending the actual event handle for this example, but you get the picture.

The handle_the_event() function relies on you and your development team sticking to a particular naming convention for any new event handler classes. If someone wanted to add functionality to our application, he or she would need to be aware that each handler's name is in the following form:

   class Handler_(Unique_Event_Handle)    {       //Handle the event with handle Unique_Event_Handle    } 

In this case, you have only two event handlers, Handler_View and Handler_Edit. The handle_the_event() method in our dispatcher class checks to see whether the relevant handler exists within the PHP namespace before creating an instance of the pertinent handler object. If not, it confesses:

   echo "I can't handle this!"; 

Notice that the instantiation of the handler passes the event handle into the constructor, in case it is needed. In the same way, you could pass an array of values here for use in the handler's processing:

   $handler_obj = new $name($this->handle); 
Note 

We don't want to concern ourselves too much with the action of the handler we simply want to pass off any information that we collect in the dispatcher to the correct handler without having to worry about what it might actually be doing with that information.

The next step is to call the handled_event() method to extract any response from the handler that might have arisen out of its handling of the event:

   $response = $handler_obj->handled_event(); 

As you have already seen the class diagram, both the existence and precise format of the handled_event() function is enforced through the use of an interface. The consequence of this is that the dispatcher can safely call the handled_event() method on its instantiated handler to retrieve a response, even if that method simply returns NULL. Once the response from the handler, if any, has been returned, the dispatcher's work is done.

We'll now take a look at the handlers' side of things.

interface.Handled.php

The interface ensures that we implement the handled_event() function in any handler classes we might developed. As mentioned previously, this is important from the perspective of the dispatcher, but if you do intend to take on larger game, this is where you can enforce the existence of any other important methods which might be relevant to your event handlers. Such methods can then either be implemented in the parent handler and overridden by the children, or simply declared abstract, passing the implementation off to the children handlers directly. As you can see from the interface code below, for our example we've stuck to simply requiring the existence of the handled_event() method.

   <?php    interface Handled    {       abstract function handled_event();    }    ?> 

The handled_event() method itself is implemented either by the parent event handler class for your application, or its children. Let's look first at the parent event handler class.

class.Event Handler.php

The parent event handler class is really more of a utility class specifying how to handle lower-level events which may occur in your application. In this specific example, it creates a database connection. The common_db.inc file contains a db_connect() function that creates a database connection pretty straightforward stuff, and the implementation of such a method is left to you. However, if you are creating an application of any serious magnitude, the best way to approach such a challenge is to make use of a database abstraction layer, as explored in Chapter 8.

Of course, you may consider adding any number of appropriate methods here depending on the nature of the problem you are solving. For now, here's the code for a simple class.Event_Handler.php:

   <?php    require_once ("interface.Handled.php");    require_once('common_db.inc');    abstract class Event_Handler    {       function dbconn(){          $link_id = db_connect('sample_db');          return $link_id;       }       abstract function handled_event();    }    ?> 

Since you are unlikely to know what each event is going to do, you declare the handled_event() function abstract so that the descendent handler classes can each implement it as they see fit. In order to allow this, you will notice that the whole class has been declared abstract. This has no real bearing on the application itself, because you will never directly instantiate this class the dispatcher will figure out which descendent event handler to instantiate, and will do so directly.

class.Handler_View.php

This descendent handler gives a quick demonstration of the type of code you can usefully execute within a typical handler. Note how you are forced to give an implementation of handled_events(), since this has been declared as an abstract method in the parent handler class. In this case, we have shown how to view a few records pulled straight from a database:

   <?php    require_once('class.Event_Handler.php');    class handler_View extends Event_Handler    {       private $handle;       function __construct($event_handle){          $this->handle = $event_handle;       }       function handled_event(){          echo "The event, $this->handle, is now handled. <BR>          It is, I promise!<BR><BR>Your records are as follows: <BR> <BR>";          $id = parent::dbconn();          $result = pg_query($id, "SELECT * FROM user");          while($query_data = pg_fetch_row($result)) {             echo "'",$query_data[1],"' is a ",$query_data[4],"<br>";          }       }    }    ?> 

Of course, the actual data being pulled out of here is irrelevant this is just an example. If you want to try it out, however, simply hook this up to an example database of your own and query it as you like, changing the SQL statement.

Note that in the constructor, the handler still holds onto the event handle passed to it, by saving it into a local private member variable. It's not actually used in this example, but it does demonstrate that you can get such parameters through to the handlers if necessary.

class.Handler_Edit.php

Let's meet another descendent handler. This demonstration of an edit handler simply returns a message to the user when invoked:

   <?php    require_once('class.Event_Handler.php');    class handler_Edit extends Event_Handler    {       private $handle;       function __construct($event_handle){          $this->handle = $event_handle;       }       function handled_event(){          echo "This is event $this->handle, which is now handled - no               kidding! <BR>";       }    }    ?> 

As usual, you capture the event handle and store it locally. In this case, the handler does actually use it as part of its event processing. Admittedly, the handled_event() method implementation is nothing special; it simply returns a message confirming that the event has been dealt with.

That's about it for setting things up. We can now explore how easy it is to modify the application and get great results with little effort.

Implementing Security

Assume that you wanted to limit the functionality of the application depending on who was using it a not uncommon requirement. An application with such a requirement could be something like a wiki, which should allow certain trusted sources to edit whatever they like but limit others to only read, and not change, the content.

First off, have the application create a new session after you have successfully validated the user. You can then populate the session variable with relevant details concerning who the user is; in this case, we have used a session variable containing the user's name. This means that you now have access to the current users' details from virtually anywhere in your application, including from within your event handlers, which your can use this information to determine whether or not to allow the event to take place at all.

Add the following method to each of the descendent event handler classes whose use you wish to restrict. As you can see, the method simply checks the author's name currently held in the session, and if the name being offered isn't acceptable, the method will consider the user unauthorized to perform the requested action:

   function secure_handler(){         if ($_SESSION['name'] == "David"){            $this->handled_event();         } else {            echo "Sorry $_SESSION['name'] you are not authorised!";         }    } 

To ensure that only the secure method is used when handling the event, you will need to change one line in the dispatcher. Open the file and change this line:

   $response = $handler_obj->handled_event(); 

to this:

   $response = $handler_obj->secure_handler(); 

You also need to ensure that you keep to the design principles we first set out by making sure that the the existence of this method in the children handlers is enforced via the use of the interface. This means that the parent handler must at least contain an abstract representation of the secure_handler() function.

Add the following line to the interface:

   abstract function secure_handler(); 

There is no way to know precisely what level of permission each event will require; it may well differ from event to event. Accordingly, the parent event handler does not directly implement the secure handler itself. Instead, much in the same way as the regular handler method we met a few pages ago, it passes responsibility for its implementation to its children, by again declaring the method abstract:

   abstract function secure_handler(); 

Each descendent event handler is now required to implement both a handled_event() and secure_handler() method as specified by the interface. To determine whether to allow the event to take place, each event handler's secure_handler() method could retrieve its own permission setting from a file held by the administrator. This file would of course be encrypted or at least held somewhere secure, and would most likely be editable by the wiki administrator, such that he or she could change which handlers were available to which users.

Here is a little example user interface that would make use of our secure, event-oriented application:

   <?php    require_once ('class.Dispatcher.php');    ?>    <HTML>       <HEAD>          <TITLE>Secure, Event Driven Record Viewer!</TITLE>       </HEAD>       <BODY>          <FORM method="GET" ACTION="<?=$_SERVER['PHP_SELF']?>">             <INPUT type="submit" name="event" value="View">             <INPUT type="submit" name="event" value="Edit">          </FORM>       </BODY>    </HTML>    <?php    function handle(){       $event = $_GET['event'];       $disp = new dispatcher($event);       $disp->handle_the_event();    }    session_start();    $_SESSION['name'] = "Horatio";    handle();    ?> 

The session variables would normally be set during the user's login, but for brevity's sake, we will assume that Horatio wants in hence why we have hard coded this in. By way of example, let's check whether we can allow Horatio to use the edit function. We quite rightly expect the following output if Horatio clicks the Edit button: (Figure 10-3).


Figure 10-3

As you can see, Horatio's attempts to edit records are stopped in their tracks, since only David may edit records.

The power of this approach should be making itself clearer now. We have looked at security as an example of how easy it is to modify the application, but this approach can apply to any number of new requirements which might come about.

Pause for Thought

Many issues need to be carefully considered when designing a well-architected, enterprise-level, event-driven application. Improvements to our event handling methodology could include some sort of handler registration, for example. This would mean that when a user logs onto a site (logging on, in this case, would be an event captured by the application), the application could automatically check which handlers are available to the user. This is achieved through the use of a default handler, which retrieves a list of the user's permissible handlers from a database and returns that information to the presentation layer.

Armed with this information, the presentation layer would know which buttons to render for the user. The administrator would get many buttons, whereas a visitor might have only one View button. If access is restricted to a view button, there is little chance that an untrusted user can maliciously modify anything to which he or she has no access.

A second issue to consider before using this method for your next big contract (albeit closely related to the first) is how to get the dispatcher to include the correct PHP project files in the first place. Because the dispatcher instantiates the event handlers, it needs to have the correct classes included. In our example, we simply included every handler class that we would need. For larger applications this approach would be wasteful because we might be including a hundred classes and using only ever one of them.

One way to rectify this problem is to mark certain handlers as being available by default, with the rest being made available on a per-user basis. Each user stored in the database would have, amongst other properties, a field containing a list of handlers available to that user. This information could be used to automatically include the relevant handlers in the dispatcher at the same time as the relevant buttons are being added to the user interface.

Finally, your various events may be fired by different entities, so you may want to consider adding functionality to determine what entity is invoking that event, and return an appropriate response based largely on that determination. This may be useful, for example, should you want to return some piece of data either in machine-readable XML format or human-readable HTML, depending on which entity has invoked the event.

One design pattern that may interest you is the reactor pattern. This deals with multisource enterprise-level event-handling and is definitely worth a look if you are serious about using events. Remember that we mentioned near the beginning of the chapter that a design pattern may exist that fits our requirements? Well, the reactor pattern is very similar to what we have done in this chapter, but has added capabilities to allow your handlers to easily differentiate between event sources. For more information on this, refer to Pattern-Oriented Software Architecture, Volume 2 (Wiley, ISBN 0-471-60695-2).



Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 182
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net