Programming - - By Maarten Manders

What’s your plan for __autoload()?

Of all magic in PHP I probably like the __autoload() hook the most. It saves a good deal of tedious script inclusion calls and may drastically speed up your application by saving the parser from doing unnecessary work. Allthough it has been around since the release of PHP5, I haven’t found any convincing applications for it yet. Most of them follow the same scheme: Whenever an undefined class is being instantiated, a little __autoload() function tries to include a PHP file, which has to be named after it’s class:


  function __autoload($name) {
    require_once('classes/'.$name.'.php'); 
  }

However, this solution is inflexible and has some drawbacks. The most obvious one is the class name -> filename constraint. Furthermore, it implies that all class files are to be stored in one single folder. That is no option for projects with several hundert classes, naturally ordered in package directories. Overall, these implementations seem not quite mature but rather a proof of concept for __autoload(). This calls for a better solution.

Loading smartly

What if we had a little ‘class finder’, which searched directories recursively for PHP scripts and parsed each one of them for class definitions? One, that would be aware of all classes in those folders and tell us which file to find them in. We could combine this with __autoload() to help it find any required class by itself. “That’s silly, it would produce way to much overhead”, you might say. That’s correct! So what if we would cache the results after each search, knowing that the file structure rarely changes, unless a developer is working on it? Of course, I’m talking about the best way of caching, thus generating the class list as PHP code and saving it for later use.

No sooner said than done. I wrote a class which implements this idea and pretentiously called it “SmartLoader”, as it’s smart enough to find any class of your PHP application without any help. You can download it here under the Lesser General Public License. Now let’s take a closer look on how it works:

Behind the scenes

Step 1: SmartLoader recursively searches for PHP scripts and parses them for containing classes with the following regular expression:

(interface|class)s+(w+)s+(extendss+(w+)s+)?(implementss+w+s*(,s*w+s*)*)?{

Step 2: We now have a list of all available classes and where to find them. This list will be converted to PHP code and written to a cache file. It’s contents will look similar to this:


// this is a automatically generated cache file.
$GLOBALS['smartloader_classes']['Main'] = 'classes/main.class.php';
$GLOBALS['smartloader_classes']['Iterable'] = 'classes/containers/iterable.class.php';
$GLOBALS['smartloader_classes']['ActiveRecord'] = 'classes/database/activerecord.class.php';
/* etc. */

Step 3: Whenever a class needs to be loaded, SmartLoader checks the cache for it’s name and includes the appropriate PHP file. In case the class cannot be found or loaded, the cache is recreated and there will be another inclusion attempt.

Getting ready

smartloader.class.php contains the autoload function as well as the actual SmartLoader class. The only thing that has to be done is to customize the autoload method:


  function __autoload($class_name) {
    /* using a static loader object rather than a singleton to reduce overhead */
    static $ldr = null;
 
    /* initializing loader */
    if(!$ldr) {
      $ldr = new SmartLoader();
    }
 
      /* defining cache file, make sure write permissions */
      $ldr->setCacheFilename('cache/smartloader_cache.php');
 
      /* adding directories to parse. better use absolute paths. */
      $ldr->addDir("classes");
 
      /* what are the endings of your class files? */
      $ldr->setClassFileEndings(array('.php', '.class'));
 
      /* should SmartLoader follow symbolic links? */
      $ldr->setfollowSymlinks(false);
 
      /* it should probably ignore hidden dirs/files */
      $ldr->setIgnoreHiddenFiles(true);
    }
 
    /* load the class or trigger some fatal error on failure */
    if(!$ldr->loadClass($class_name)) {
      trigger_error("Cannot load class '".$class_name."'", E_USER_ERROR);
    }
  }

After that you just need to include smartloader.class.php in your scripts and never worry about class includes anymore.

Advantages

  • Convenience: SmartLoader simplifies class file management. You can rename them, move them around or reorganize them in (package) folders. As long as they are in your webspace, SmartLoader can find them.
  • Speed: With SmartLoader, class files are only loaded when they are actually needed. This approach called “lazy loading” or “just in time” (not to confuse with interactive programming) can save PHP a lot of parsing and compiling caused by redundant inclusions.
  • Portability, Backwards Compatibility: You can easily adopt SmartLoader without breaking your existing applications. __autoload() only gets involved when a non-existent class is being instantiated by default.

Potential pitfalls

  • Error handling: You may use any flavor of error handling you like. Well, almost: Exceptions can’t raise through __autoload(). Remember, that’s a feature, not a bug. It is possible though with a scary eval() hack but I won’t go further into that.
  • Version control systems: If your webspace is a working copy of some version control system you should probably tell SmartLoader to ignore hidden files. This will improve it’s performance and prevent it from scanning those spicious outdated copies of files sitting in the .SVN/.CVS folders.
  • I shouldn’t be telling you that, but don’t let SmartLoader scan symlink loops.
  • Profiling: Zend Profiler gets confused by SmartLoader and starts calculating invalid results for it’s execution time.
  • Opcode caches: I don’t know the inner workings of APC, Zend Optimizer and the likes well enough, but I can imagine that dynamic inclusions as used in SmartLoader negate the performance gained by opcode caching. I haven’t been able to verify this, since Zend Profiler refuses to work properly with SmartLoader (see above). I’d be happy to receive any feedback on this matter.
Sponsors