PHP Package Management and Autoloading

Jacob Wright
September 19th, 2005

In PHP 5 and greater there is a magic function called __autoload (note: two underscores). __autoload allows us to load a class on the fly so to speak. It only gets called when a class can not be found, then it has a chance to load the class. If the class still can not be found, you get your regular error stating so. __autoload can be really useful if all of your classes happen to be in one directory, but what happens if you are managing a large project with many packages of classes? We will look at some different options.

Single Directory of Classes

This is the simplest solution, and the best if you have a small project. You may have situations where you would like to load the class without creating a new instance of it, so I will be creating a loadClass function in each example which __autoload will call. That way you may load a class manually. Here is what you might do if all of your classes are in one directory:

function __autoload($className) {
    loadClass($className);
}

function loadClass($className) {
    include_once('classes/' . $className . '.php');
}

Pretty Simple.

Packages of Classes

Now how about package management, with many directories of classes? A few developers have named their classes with the package names in the class name. For example, util_Parser would be the Parser class in the util directory (in your base classes directory), and util_html_HTMLParser would be the HTML parser class in the util.html package. Here is an example of how that would work:

function __autoload($className) {
    loadClass($className);
}

function loadClass($className) {
    include_once('classes/' . str_replace('_', '/', $className) . '.php');
}

Simple like the first method, but we get packages now. However, we have to actually name our classes with the package names in front of it. We start to get very long names for some of our classes. If we extended the Parser class we might have util_html_HTMLParser and util_xml_XMLParser or perhaps even deeper packages. We also find refactoring difficult because if a class must move to another package, we must go through all our code and rename the class everywhere we use it.

Package Management Using import

There is another way, although not as simple, which allows packages as deep as you would like, and requires minimal work when refactoring. This is the import way of doing things. import is a function which keeps track of what classes you might use in a certain script/class and then uses __autoload to look up the location of the class. Here's how it works.

  • You import the class or package using the import function like this, import('util.html.HTMLParser');, at the top of your script
  • import stores the name of the class, 'HTMLParser', and the path to the class, 'util/html/HTMLParser.php', in a global associative array called imports
  • When the class is utilized, __autoload will do a lookup in the imports array for the class and load it in.

Here is how the code looks. It is a bit more complicated, but very nice to use for a larger system:

function __autoload($className) {
    loadClass($className);
}

function loadClass($className) {
    global $imports;
    if (isset($imports[$className])) {
        include_once($imports[$className]);
    }
}

$imports = array();
function import($import) {
    global $imports;
   
    // seperate import into a package and a class
    $lastDot = strrpos($import, '.');
    $class = $lastDot ? substr($import, $lastDot + 1) : $import;
    $package = substr($import, 0, $lastDot);
   
    // if this import has already happened, return true
    if (isset($imports[$class]) || isset($imports[$package.'.*'])) return true;
   
    // create a folder path out of the package name
    $folder = '' . ($package ? str_replace('.', '/', $package) : '');
    $file = "$folder/$class.php";
   
    // make sure the folder exists
    if (!file_exists($folder)) {
        $back = debug_backtrace();
        return trigger_error("There is no such package <strong>'$package'</strong> -- Checked folder <strong>'$folder'</strong><br />
            Imported from <strong>'{$back[0]['file']}'</strong> on line <strong>'{$back[0]['line']}'</strong><br />"
, E_USER_WARNING);
    } elseif ($class != '*' && !file_exists($file)) {
        $back = debug_backtrace();
        return trigger_error("There is no such Class <strong>'$import'</strong> -- Checked for file <strong>'$file'</strong><br />
            Imported from <strong>'{$back[0]['file']}'</strong> on line <strong>'{$back[0]['line']}'</strong><br />"
, E_USER_WARNING);
    }
   
    if ($class != '*') {
        // add the class and it's file location to the imports array
        $imports[$class] = $file;
    } else {
        // add all the classes from this package and their file location to the imports array
        // first log the fact that this whole package was alread imported
        $imports["$package.*"] = 1;
        $dir = opendir($folder);
        while (($file = readdir($dir)) !== false) {
            if (strrpos($file, '.php')) {
                $class = str_replace('.php', '', $file);
                // put it in the import array!
                $imports[$class] = "$folder/$file";
            }
        }
    }
}

There you have it. A powerful way to manage your packages and load only classes which are needed for use. Also, a feature of this method of autoloading classes is that you can use an asterisk, *, to import all the classes in a package. And it only loads the classes needed because of __autoload.

Finally, this is how you would use the import method in your scripts:

import('util.html.HTMLParser');
import('template.arras.*');

// here would be all of your important code
// not worrying about including files hwen you need them
// because you've already set it up to include them
// automatically when you use them, and only
// _if_ you use them
$p = new HTMLParser();
$t = new ArrasTemplate();
// etc.

6 Responses to “PHP Package Management and Autoloading”

  1. Neill Jones Says:

    Thanks for this - solved my problem almost exactly. I wasn’t interested in packages, just stopping loads of classes being loaded until they were needed and wanted so this was pretty much what I wanted.

    Thanks again

  2. Vance Dubberly Says:

    It should prolly be noted, since at some point someone’s going to use this and watch their app explode ( especially since alot of people are attempting to write real programs with PHP these days), that this is package management for the sake file organization but it doesn’t provide the name space protection of true packages. So if you have two classes in two packages called… oh lets say, “database.Record” and “media.music.Record” you’re SOL. Someday PHP will grow up, but until then this is a nice hack for keeping things organized.

  3. Jacob Wright Says:

    Very true Vance. The first method is safer if you name the classes package_subpackage_Class. Of course, if you’re using existing libraries and you’ve got a database Record class and a music Record class your SOL whether you use this way of importing or not.

  4. Howie Says:

    Interesting. Coming from a Java background, and now learning to use PHP5, this solution suits me.

    One thought re name collision.. why not name the php file com.mycompany.mypackage.myclass.php ? i.e use the package name in the filename, rather than the directory structure? This way, all clases live in one folder, but have a unique name

    Just a thought

  5. Jacob Wright Says:

    Having all the classes in one folder would drive me crazy. But you could certainly modify the code to do that. :) Wouldn’t be difficult.

  6. ujjwal soni Says:

    Its really intresting. I am a J2EE developer, really liked this article.

    Cheers,

    Ujjwal B Soni

Leave a Reply