Lazy PHP: Part 3

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://fashion.hosmoz.net actarus

    w00t ! it rocks. but just an idea, wouldn’t it be better in ResolvHandle() to use an eval() for the object call ? i mean, for the arguments, instead of the switch, something like:

    $code = ‘$Handle = new $Class(‘;
    $code .= implode(‘, ‘, $ConstructionArgs);
    $code .= ‘);’;

    eval($code);

  • http://fashion.hosmoz.net actarus

    i meant:

    $code = '$Handle = new $Class(';
    $code .= implode(', ', $ConstructionArgs);
    $code .= ');';

    eval($code);

    sorry for the noise :) but heh, there is no way to preview the comment before posting :/

  • sevengraff

    I think that is how it is done in Pear’s Form (not quick form)

  • Alan Knowles

    reminds me of your last post on Oo gone mad…

    trying to solve a simple problem by adding layers and layers of code..
    a) dont use PHP construtors! for objects with args. xxxxx::construct(….)
    b) require before use..

    Regards
    Alan

  • http://www.phppatterns.com HarryF

    wouldn’t it be better in ResolvHandle() to use an eval() for the object call ?

    I guess yes but as Jeff puts it here: “I am prejudiced against eval.” which is a point of view I can understand, from my perspective because I don’t think I’d ever be able to trust it 100% and be nervous every time PHP changes version. Something like ResolveHandle is likely to get embedded in alot of code so having it break would be painful.

    The three argument limit to the constructor seems like a rough edge (although it’s easy to increase). Generally though, who send more than about 5 to a constructor?

  • http://www.procata.com/ Selkirk

    Alan,

    This handle mechanism is only intended to be used in a limited set of circumstances. That is when creating an object that is intended to control interaction with a user across a series of requests. The handle mechanism provides a way to compose a single assembly of objects for the interaction, and yet only instantiate the objects needed for the particular request being handled at the moment. It is an optimization adaption to PHP’s lack of ability to preserve state between requests.

    If you do not wish to use this optimization, you can instead include all of the files necessary and instantiate all of the objects in the assembly on every request. This results in simpler code and is a perfectly valid way to use this mechanism in WACT. The API for doing this is the same. I took the simpler approach in [URL=http://www.sitepoint.com/forums/showpost.php?p=1208767&postcount=43]this post[/URL]. rather than try to explain the handle mechanism, it just didn’t use it.

    The syntax [CODE]classfile.php|class[/CODE] is meant to be the PHP equivalent to a fully qualified java class specification: [CODE]x.y.z.class[/CODE].

    The handle design replaced a template method based design and favors a composition based approach rather than an inheritance based approach for using WACT controllers. Based on the typical usage so far, this approach is better than the previous approach we used.

    That said, the controller design from WACT is far from set in stone and far from finished. There may be a better design to accomplish the same thing that remains undiscovered. Also, I have not yet done benchmarking to determine if the optimization this provides is worth the extra complexity, or if it the benefit of lazy loading even outweighs the costs of the ResolveHandle function for common examples. if it turns out that it does not, I would happily consider removing this from WACT in favor of simplicity.

  • Ren

    Why was | used rather than #?

    userupdateinitial.php#UserUpdateInitial

    just to make it more like a URL, and then possibly could get ResolveHandle() creating proxies, for services/classes elsewhere.

  • http://www.phppatterns.com HarryF

    Why was | used rather than #?

    Because : is used in Windows paths…;) actually don’t think we though of # – good idea – like the remote proxy angle.

  • dartroce

    domaceltrocd