Programming
Article
By Harry Fuecks

Lazy PHP: Part 3

By Harry Fuecks

Following on from the last blog on Laziness in PHP,
some more Lazy Initialization, this time applied to class loading.

In most programming languages, objects are “expensive” in terms of performance. In PHP, the “cost of objects” is felt even more, thanks to PHP ditching it’s memory between page requests (see Part 1) among other things.

To demonstrate the point, here’s a simple form controlling class (based loosely on the form controllers in WACT);


class FormController {
    var $initial_action;
    var $invalid_action;
    var $valid_action;
    var $validator;
    /**
    * This action will be performed if the form has not yet
    * been posted
    */
    function registerInitialAction(& $action) {
        $this->initial_action = & $action;
    }
    /**
    * This action is performed if the form IS posted by
    * invalid data was submitted
    */
    function registerInvalidAction(& $action) {
        $this->invalid_action = & $action;
    }
    /**
    * This action is performed if the form IS posted and
    * the submitted data was validated
    */
    function registerValidAction(& $action) {
        $this->valid_action = & $action;
    }
    /**
    * Registers the object for validation
    */
    function registerValidator(& $validator) {
        $this->validator = & $validator;
    }
    /**
    * Run the form controller
    */
    function run() {
        # Has the form been posted?
        if ( !strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') ) {
        
            # No - perform the initial action
            $this->initial_action->perform();
            
        } else {
        
            # Yes - need to validate
            if ( ! $this->validator->isValid() ) {
            
                # Invalid data - perform the invalid action
                $this->invalid_action->perform();
                
            } else {
            
                # All OK - perform the valid action
                $this->valid_action->perform();
            }
        }
    }
}

To use this controller, I define my own “action” classes which contain the method perform e.g.;


class UserUpdateInitial {
    function perform() {
        // display the HTML form
    }
}

and


class UserUpdateValid {
    function perform() {
        // pull data from $_POST and update database
        // display a some HTML saying "success"
    }
}

Then I can use the form controller like;


< ?php
// Load all the necessary classes
require_once 'lib/controller/formcontroller.php';
require_once 'lib/action/userupdateinitial.php';
require_once 'lib/action/userupdateinvalid.php';
require_once 'lib/action/userupdatevalid.php';
require_once 'lib/validator/validator.php';

// Create the FormController
$F = & new FormController();

// Register the actions
$F->registerInitialAction(new UserUpdateInitial());
$F->registerInvalidAction(new UserUpdateInvalid());
$F->registerInvalidAction(new UserUpdateValid());

// Validation
$V = & new Validator();
$V->addRule(new SizeRule('username',1,15));
$V->addRule(new MatchRule('password','confirmpassword'));
$F->registerValidator($V);

// Run the form controller
$F->run();
?>

If this is going over your head, take a moment to examine the FormController::run() method above and you should see how this fits together.

The Validator class and the “Rules” I’ll leave as imaginary code to keep this focused.

So far so good. The form control and validation classes are now code I can re-use for any (simple) form, allowing me to focus on developing the “actions” which are specific to this form.

But when it comes to efficiency, there’s a problem. If you look at the FormController::run() you’ll notice a few things;

  • The Validator class is only being used if the form is actually submitted. In the form’s initial state (displaying the empty form), I don’t need the validator (there’s nothing to valid yet).
  • – Only one action is ever performed for a single page request. But for each page request, I’m creating three “action objects”, two of which aren’t used.
  • Perhaps for this simple example I can get away with it but for a complex form with multiple pages and lots of fields to validate, combined with all sorts of other “infrastructure” classes (db access etc.), it quickly get’s too expensive.
  • So can I improve things? For the answer, enter Jeff who brought up the subject a while ago in Object Composition by Proxy. The result of that discussion can be found in WACT in the form of the ResolveHandle function (found in WACT: framework/util/handle.inc.php);

    
    < ?php
    //--------------------------------------------------------------------------------
    // Copyright 2003 Procata, Inc.
    // Released under the LGPL license (http://www.gnu.org/copyleft/lesser.html)
    //--------------------------------------------------------------------------------
    /**
    * @package WACT_UTIL
    * @version $Id: handle.inc.php,v 1.1 2004/03/10 01:53:49 jeffmoore Exp $
    */
    /**
    * Takes a "handle" to an object and modifies it to convert it to an instance
    * of the class. Allows for "lazy loading" of objects on demand.
    * @see http://wact.sourceforge.net/index.php/ResolveHandle
    * @param mixed
    * @return void
    * @access public
    * @package WACT
    */
    function ResolveHandle(&$Handle) {
        if (!is_object($Handle) && !is_null($Handle)) {
            if (is_array($Handle)) {
                $Class = array_shift($Handle);
                $ConstructionArgs = $Handle;
            } else {
                $ConstructionArgs = array();
                $Class = $Handle;
            }
            if (is_integer($Pos = strpos($Class, '|'))) {
                $File = substr($Class, 0, $Pos);
                $Class = substr($Class, $Pos + 1);
                require_once $File;
            }
            switch (count($ConstructionArgs)) {
            case 0:
                $Handle = new $Class();  // =& doesn't work here.  Why?
                break;
            case 1:
                $Handle = new $Class(array_shift($ConstructionArgs));
                break;
            case 2:
                $Handle = new $Class(
                    array_shift($ConstructionArgs), 
                    array_shift($ConstructionArgs));
                break;
            case 3:
                $Handle = new $Class(
                    array_shift($ConstructionArgs), 
                    array_shift($ConstructionArgs), 
                    array_shift($ConstructionArgs));
                break;
            default:
                // Too many arguments for this cobbled together implemenentation.  :(
                die();
            }
        }
    }
    ?>
    
    

    What this mysterious looking function does is provide an additional “protocol” for referencing objects in addition to using normal PHP variable assignment. Put another way, rather than this;

    
    require_once 'lib/action/userupdateinitial.php';
    //...
    $F->registerInitialAction(new UserUpdateInitial());
    
    

    I can now do just this;

    
    $F->registerInitialAction('lib/action/userupdateinitial.php|UserUpdateInitial');
    
    

    Instead of registering the object itself, what I’ve registered is just a string containing a filename and a classname which can be “resolved” later, when this object is needed.

    I need to update the FormController::run() method to take advantage of ResolveHandle();

    (UPDATE): Change to the script below… original usage of ResolveHandle was incorrect

    
        /**
        * Run the form controller
        */
        function run() {
            if ( !strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') ) {
            
                // Resolve the InitialAction
                ResolveHandle($this->initial_action);
                $this->initial_action->peform();
                
            } else {
                // Resolve the Validator
                ResolveHandle($this->validator);
                
                if ( ! $this->validator->isValid() ) {
                
                    // Resolve the InvalidAction
                    ResolveHandle($this->invalid_action);
                    $this->invalid_action->perform();
                    
                } else {
                
                    // Resolve the ValidAction
                    ResolveHandle($this->valid_action);
                    $this->valid_action->perform();
                }
            }
        }
    
    

    Using the ResolveHandle() function, the objects are only created as they are needed, cutting the performance overhead down to only those objects required to handle the current request.

    Note I could have simply “registered” class and file names for this particular example but Jeff’s ResolveHandle() is more flexible in that it supports a number of different types of “reference” (including a normal reference to a real object) as well as passing objects to the constructor. Further examples can be found at http://wact.sourceforge.net/index.php/ResolveHandle.

    Although I focused on form processing in this example, the general principle could be extended to other problem domains in PHP applications, from Object Relational Mapping to building a Domain Model. More on those some other time.

Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account