SmartLoader Reloaded

A few months ago I proposed a way to efficiently use __autoload() together with a class indexer. To my surprise, quite some people started using it and provided me with bug reports. I rewrote most of the original code, eliminated all known bugs and have been testing it for the last month. So here’s a new version with some improvements:

  • Added ability to scan include directories, too
  • Using SPL RecursiveDirectoryIterator for the indexer/scanner
  • Optimized SmartLoader::load(). It now takes < 1 ms to complete (with inclusion) on my machine
  • Windows support through DIRECTORY_SEPARATOR constant
  • More information in index file headers, like:
  • 
        * Created by:    /usr/local/php5/lib/php/SmartLoader/SmartLoader.class.php
        * Created at:    Thu, 06 Apr 2006 23:54:48 +0200
        * Scanned:       134 directories and 402 files in 1.15 seconds.
    
  • Fixed various bugs

Backwards compatibility?

Robert Schmelzer dug up the most remarkable bug. As of PHP 5.1.2 this piece of code won’t work anymore:


preg_match_all('/hand/', 'Talk to the hand!', $result = array()));

The $result variable will be empty unless I remove the “= array()”. Why?

Of course, one could argue about how much sense initializing the result as array makes. From my point of view it’s about making the code more readable by showing that I’m passing an empty array to be filled. I suppose Derick, on the other hand, would call it “doing something silly“. ;)

How to use

The best way to use SmartLoader is to put the SVN trunk’s contents into your include directory (don’t forget write access for SmartLoader/indexfiles). After that you can easily set it up in your scripts by adding these two lines:


require('smartloader.php');
SmartLoader::addIncludeDir(dirname(__FILE__).'/../'); /* (or just the document root) */

I strongly recommend prefixing your classes, especially when using the handy include directory scanning or else you’ll get name collisions. Maybe PHP namespaces will solve this problem somewhen.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • moraes

    I have been using it for a while and it is very useful. The biggest annoyance is when a class is not found – in a big system, sometimes you don’t know *where* it was called.

  • Alan Knowles

    haha – you’ve re-invented include_path ;)

    no more wondering where a file is included from, you just have to read the mind of the author and hope he hasnt hidden away the SmartLoader call in some obscure include file.

  • auricle

    > the most remarkable bug

    In case anyone was wondering, this does work:
    class Test {
    function __construct($a) {
    print_r($a);
    }
    }

    $test = new Test($a = array('hello'));

  • Ben

    Why not replace the $GLOBALS[] usage with a static property as it should be private to the class.

  • benpjohnson

    Also it might be good to use the for loop instead of foreach as I remember it being *much* faster in the PHP benchmark I read somewhere…

    $size=sizeof($something)

    for($i=0; $i

  • http://www.phpism.net Maarten Manders

    haha—you’ve re-invented include_path ;)

    Great, isn’t it? Let’s call it include_path on crack! ;)

    Why not replace the $GLOBALS[] usage with a static property as it should be private to the class.

    I can’t put it into SmartLoader itself because it is defined somewhere else and I can’t define a class (or parts of it) twice. I could put it into some SmartLoaderIndex::index variable but that’s just another (and fashionable) way to make it global.

    Also it might be good to use the for loop instead of foreach as I remember it being *much* faster in the PHP benchmark I read somewhere…

    For-loops are about 3 times faster, that’s correct. But don’t make the mistake to write something like this:

    for($i=0; $i<sizeof($array); $i++)

    The sizeof function would be executed for every iteration, which makes a loop about 10x slower than a foreach.

    However, there are no performance-critical loops in SmartLoader. Most of the time (while indexing, which happens once per software revision!) is being used for preg_match_all (I could try to improve the pattern’s performance) and PHP function calls (which are horribly slow). You can’t use for-loops on associative arrays, either.

  • mwolfe

    and then there is the obvious performance booster:
    $total = sizeof($array);
    for($i=0; $i

  • mwolfe

    sorry

    $total = sizeof($array);
    for($i=0; $i

  • http://www.realityedge.com.au mrsmiley

    The sizeof function would be executed for every iteration, which makes a loop about 10x slower than a foreach.

    I hope this isn’t the case. I used to think that until I ran some tests a while back and it’s only executed on the first iteration. My test was something like

    $test = array(…..);
    for ($i=0; $i”prior” to adding more elements inside it.

    I believe you only experience said effect while using something like while() and each().

  • http://www.phpism.net Maarten Manders

    mrsmiley, you can easily check that with the following code (or just XDebug/Cachegrind):

    
    for($i = 0; test() && $i < 5; $i++) { /* ... */ }
    
    function test() {
    	echo 'Test!
    '; return true; }
  • kyberfabrikken

    An optimizer might be able fix the loop, but vanilla PHP doesn’t have an optimizer. I always write for-loops as this to be sure :

    for ($i=0, $l=count($a); $i < $l; ++$i) {
    }

    Using ++$i rather than $i++ also saves an internal buffer.

  • http://forums.teamphoenixrising.net Mincer

    Maarten,

    I found a little ‘quirk’ in the regex pattern, in that it matches commented code.

    Not only would this be an issue if it thought it could find a class that had been commented out, but also you could inadvertantly overwrite the location of a real class in the index file while writing examples in another file that was scanned elsewhere in the indexing process.

    E.g.

    File 1:

    /**
     * FooBar class
     *
     * Not bothered what I write here, as it's a comment...
     *
     * Example Usage:
     * 
     * class MyClass extends FooBar {}
     *
     */
    

    File 2:

    class MyClass
    {
        $foo = bar;
    }

    The index thinks the class can be found in File 1, rather than File 2.

    Matt.

  • http://forums.teamphoenixrising.net Mincer

    Oops, let’s try that again….

    (why no preview for comments???)

    Maarten,

    I found a little ‘quirk’ in the regex pattern, in that it matches commented code.

    Not only would this be an issue if it thought it could find a class that had been commented out, but also you could inadvertantly overwrite the location of a real class in the index file while writing examples in another file that was scanned elsewhere in the indexing process.

    E.g.

    File 1:

    /**
    * FooBar class
    *
    * Not bothered what I write here, as it’s a comment…
    *
    * Example Usage:
    *
    * class MyClass extends FooBar {}
    *
    */

    File 2:

    class MyClass
    {
    $foo = bar;
    }

    The index thinks the class can be found in File 1, rather than File 2.

    Matt.

  • michel

    Probably extremely late to the party…

    Mincer:
    > I found a little ‘quirk’ in the regex pattern,
    > in that it matches commented code.

    My concern as well, though it’s easily fixed with a little token_get_all(), T_CLASS and T_INTERFACE.

  • Jann

    Hmm, the download link ist not available anymore, can i get the smartloader update elsewhere?

  • Christiaan

    Download link still ain’t available, I also would like to try out this new version. Especially since you’ve mentioned the DIRECTORY_SEPERATOR, which probably was why it wasn’t working for me before on Windows.