Programming
Article
By Harry Fuecks

Lazy PHP: Part 1

By Harry Fuecks

There’s alot to be said for laziness, particularily when it comes to PHP. I’m not talking about stretching out on the sofa but rather techniques you can employ to make your PHP code faster.

How PHP works (from a distance)
When a PHP script is requested from your server, a number of things happen before the complete web page arrives at the visitors browser. A simplistic view is something like;

1. Incoming Request: The web server (usually Apache) receives request for script and “delegates” the work to the PHP’s core engine (the Zend Engine). If you’re using PHP as, say, an Apache module, it’s already loaded and ready for action. If you’re using PHP as a CGI executable, it first has to “whir up” before it can do any work.

2. Prepare OPCODEs: The engine reads, parses and compiles the script into “OPCODEs” (a list of instructions for what to do when it comes to execution)

3. Execution: The instructions are executed and the end result (a web page) arrives at the visitors browser.

4. Trash the lot: The PHP engine chucks away everything; opcodes, variables held in memory, output – the lot. Back to step 1…

There’s a more detailed explaination in A HOWTO on Optimizing PHP and a very detailed discussion in PHP Inside Out (PDF).

I’ll get to OPCODE caching in a moment but thats the general picture.

Some may say the last part, especially the ditching of all variables held in memory, is insane. In practice it means you hardly ever run into resource allocation issues (e.g. lack of memory) with PHP plus it’s very easy to deploy PHP applications across multiple servers and balance the load between them.

The bottom line is there’s alot of waste. Your application get’s “re-born” with each new hit a script takes.

Lazy PHP
So it’s clear that the less work the PHP engine has to do, the quicker a visitor gets the web page they asked for.

But if you’ve got a complex application offering all sort of functionality, using database abstraction, web services, user authentication etc. etc. how can hope to have decent performance?!?

The answer is laziness: doing absolute minimum.

For example, why load all 1000 lines of error handling logic if there were no errors to handle? Why include that library of 101 functions if only one got called for the current request? Why fetch an entire table into a PHP array if you’re only going to display 10 records at a time? Why re-render HTML if the content hasn’t changed?

The trick to Lazy PHP is designing your code so that there’s the minimum “footprint” required to serve the current request and “pointers” to where to find the rest, if it’s needed.

Lazy Includes
Typically, when you deploy a PHP script to your web server, it isn’t going to change very often (only when you replace it with a new version). Going back to the four steps above, it’s suddenly clear that preparing OPCODEs only needs to be done once for each version of a PHP script; the first time it’s executed.

If you’re using some form of OPCODE cache such as Zend Accelerator, PHP Accelerator or Turck MMCache (nice for Windows users), you get to bypass step 2, after the first execution, the OPCODEs being cached for future use.

Sadly few of the cheap LAMP hosts out there seem to be aware of OPCODE caching (or that some are Open Source / free to use) so the majority of us have to put up with the cost of reading, parsing and compiling that comes with every a PHP script takes.

One handy way to get round this though is by careful placement of commands like include and require_once.

For example what if you have an authentication system where you want to keep track of failed attempts to log in. You might have code something like;


< ?php
require_once 'lib/auth.php';
require_once 'lib/logger.php';

$Auth = & new Auth();
$Logger = & new Logger('auth.log');

if ( $Auth->isValid($_POST['username'],$_POST['password']) ) {
    // The user is valid
} else {
    $message = "Invalid username / password for ".
        $_POST['username']." from ".$_SERVER['REMOTE_HOST'];
    $Logger->log($message);
}
?>

The logger is getting loaded (and instantiated) on every request, but it only gets used if there’s a failed login. How about;


< ?php
require_once 'lib/auth.php';

$Auth = & new Auth();

if ( $Auth->isValid($_POST['username'],$_POST['password']) ) {
    // The user is valid - the stuff here
} else {
    // Now load the logger...
    require_once 'lib/logger.php';
    $Logger = & new Logger('auth.log');
    $message = "Invalid username / password for ".
        $_POST['username']." from ".$_SERVER['REMOTE_HOST'];
    $Logger->log($message);
}
?>

Now the logger is only included when there’s something that needs logging.

What’s more, you can include functions and classes from inside a function, so the following is also possible;


< ?php
function updateArticle(& $User, $article) {
    // Include a class
    require_once 'lib/permissions.php';
    $Permissions = & new Permissions($User);

    $action = 'update_article';
    if ( $Permissions->actionAllowed($action) ) {
        // Update the article - store in DB for example
    } else {
        // Include a class
        require_once 'lib/logger.php';
        $Logger = & new Logger ('useractions.log');
        $message = "Illegal action: $action attempted by ".
            $User->username();
        $Logger->log($message);
    }
}

[Note the the above code is not meant to suggest any particular logging / authentication strategy]

Lazy includes become particularily important when writing OO PHP, where there may, potentially, be hundreds of classes in your application, all in seperate files. Limiting the number of files included to only those actually needed to serve a given request keeps the overhead down.

Making sense?

[…to be continued…]

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