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:
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:
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
importfunction like this,import('util.html.HTMLParser');, at the top of your script importstores the name of the class, 'HTMLParser', and the path to the class, 'util/html/HTMLParser.php', in a global associative array calledimports- When the class is utilized,
__autoloadwill do a lookup in theimportsarray 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:
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('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.
May 15th, 2006 at 7:09 pm
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
May 13th, 2008 at 9:10 pm
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.
May 16th, 2008 at 11:07 am
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.
June 19th, 2008 at 10:52 am
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
June 19th, 2008 at 11:12 pm
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.
August 18th, 2008 at 4:54 am
Its really intresting. I am a J2EE developer, really liked this article.
Cheers,
Ujjwal B Soni