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
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:
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.
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
November 24th, 2008 at 10:35 am
I’ve added you to a “Thanks to” page on our website. It’s proved very useful to us.
The link is http://www.whosup4.com/anon/technology/
Thanks again.
December 26th, 2008 at 6:11 am
Hi,
it’s interesting, many thanks.
i need further feature if possible; suppose test1.php is in the folowing directory myProject/test/views/test1.php; and my class Class1.php is in myProject/classes/Class1.php, so when using your function in test1.php like this:
import(classes.Class1.php) i got an error saying no folder… the error is clear because test1.php looks for, from its current directory (views), the directory classes and it doesn’t find it. how can i overcome this problem?
regards
March 22nd, 2009 at 12:48 pm
PHP 5.3 is introducing name space support. More details are here :-
http://blog.agoraproduction.com/index.php?/archives/47-PHP-Namespaces-Part-1-Basic-usage-gotchas.html
and here
http://developers.slashdot.org
and here
/article.pl?sid=08/10/26/1610259&from=rss
http://wiki.php.net/rfc/namespaceseparator
I believe doing an import without name spaces support will not work for large code bases. Till the time sticking to X_Y_Z pattern is safer.
October 12th, 2009 at 4:47 pm
You have done really good job, thanx, its very useful for my current project, thanx once again, can you tell me that the function import is working actually include() all class file?
October 12th, 2009 at 5:44 pm
if you do an import(‘mypackage.*’); it will “register” all the filenames so that when a class is referenced it can know where to include() the PHP from. include() only happens when actually using the class you need. However, using “*” in the import will scan the directory for the filenames, so use sparingly.
October 17th, 2009 at 1:48 pm
thanks, great job, this really gave me some good ideas for my projects, its very interesting.
for that thing of “*” you could use some regular expressions instead of strrpos to make it more restrictive with the uppercase convention and the “.php” ending extension, something like this:
if (preg_match(‘/^[A-Z](?.*).php$/’, $file,$class)) {
$imports[$class['name']] = “$folder/$file”;
}
October 17th, 2009 at 1:55 pm
well i repeat the code in another for cause the system erase the name tag after the “?” haha, hopes this comes useful
if (preg_match(’/^[A-Z](.*).php$/’, $file,$class)) {
$imports[$class[1]] = “$folder/$file”;
}
thanks again.
December 31st, 2009 at 6:22 pm
Thanks, this is very cool. Is it available for use via a public license? Without one explicitly given it’s technically not legal to reuse since you can claim copyright at any time (an implicit copyright.)
A few example licenses:
MIT License allows for most use but you must include the copyright notice every time: http://www.opensource.org/licenses/mit-license.php
Public domain has no restrictions at all: http://creativecommons.org/licenses/publicdomain/
Anyway, thanks a lot!
December 31st, 2009 at 5:06 pm
This is under the creative commons Attribution license (http://creativecommons.org/licenses/by/3.0/).
January 3rd, 2010 at 4:53 am
how i can get the php package dawnlode
January 6th, 2010 at 10:16 pm
anisa: copy/paste into a file.
April 11th, 2010 at 7:44 am
My own derivation from your code:
The following is placed in classes/loader.inc.php and included via require_once(‘classes/loader.inc.php’);
It will turn this
new TagStorageData()
into
require_once(‘classes/tag/storage/tag_storage_data.class.php’);
and
new ITagStorageData()
into
require_once(‘classes/tag/storage/tag_storage_data.interface.php’);
define(‘CLASS_EXT’, ‘.class.php’);
define(‘INTERFACE_EXT’, ‘.interface.php’);
function __autoload ($className) {
loadClass($className);
}
function loadClass ($className) {
$classesRootFolder = dirname(__FILE__);
$classFileExtension = ”;
if(preg_match(‘/^I[A-Z]/’, $className)) { // Interface
$classFileExtension = INTERFACE_EXT;
$className = preg_replace(‘/^I([A-Z])/’, ‘$1′, $className);
} else { // Class
$classFileExtension = CLASS_EXT;
}
$parts = preg_split(‘/(?<!^)(?=[A-Z])/', $className);
$classFileName = strtolower(implode('_', $parts).$classFileExtension);
array_pop($parts); // Drop final element
$classFileName = strtolower(implode('/', $parts)).'/'.$classFileName;
echo "Auto-loading Class: $className in $classFileName\n";
$classFileName = $classesRootFolder.'/'.$classFileName;
include_once($classFileName);
}
May 3rd, 2010 at 2:48 am
Jacob’s solution can be misleading in two ways.
Firstly, it creates the illusion that there are some kind of packages in PHP, which is not the case, so the problem of name collisions remains. Howie have already pointed that out.
Secondly, declaring “imported” classes at the top of the file does not imply that those are the only dependencies required by the script. Some classes might already have loaded other classes too. It’s kind of a hint, though.
Personally, since there is no notion of a package in PHP (though in PHP 5.3 namespaces are introduced), i find there is no need to specify the exact location of the “imported class”. There is no way for explicitly declaring the dependencies either. So, basically the only things i want to (and can) achieve are:
1. Get rid of the require or include statements
2. Organize files in some kind of hierarchy
The idea is just to build the include_path to contain all the subdirectories. That way i can easily move files around without worrying about any class loading issues.
init();
function init() {
$path = implode(dirs(“classes”), PATH_SEPARATOR);
set_include_path($path);
main();
}
function dirs($path) {
$dirs = array($path);
$contents = scandir($path);
foreach ($contents as $filename) {
if (ignored($filename)) {
continue;
}
$item = “{$path}/{$filename}”;
if (is_dir($item)) {
$dirs = array_merge($dirs, dirs($item));
}
}
return $dirs;
}
function ignored($filename) {
return preg_match(“/^\./”, $filename);
}
function __autoload($class) {
require_once(“{$class}.php”);
}
June 9th, 2010 at 11:38 am
I prefer Andrey’s solution. I see no need to explicitly list directories. Manipulate the include_path to include all the directories where files would be located.
Consider whether a solution like this is really needed before implementing it.
Lastly, I think it would be a mistake to try to make PHP look like Java.
September 13th, 2010 at 1:37 pm
Cool, I wasn’t aware of this function
April 13th, 2011 at 2:47 am
here u haven’t defined how to actually define package.
as in java u can define package as..
package packageName;
please help me.
April 13th, 2011 at 10:43 am
varsha: The package is the folder path. There is no package declaration.
ryan: the goal isn’t to look like Java, but to make loading php files with classes easier. But it isn’t for everyone. And now PHP has namespaces!
July 10th, 2011 at 1:20 pm
Hey,
pretty cool way to simulate packages in PHP. As for me I learned PHP first and then stuff like Java, C# Scala etc. And always missed the packages in PHP(and the namespaces are not very usefull for this). So I really like your solution, but for larger project it wouldn’t be effective to write the code in every class.
November 29th, 2011 at 2:47 pm
Thanks for this great solution. Been missing this in PHP.
And Andreas, there’s no need to include the code in every class, as long as it’s included once in the documents calling the classes in the library (like initializing the library in a main document).
January 2nd, 2012 at 1:29 am
thanks , this is awesome :)
March 7th, 2012 at 8:31 pm
[...] blog do Jacob Wright, tem um artigo super legal desenvolvendo uma função mais elaborada de classloader, recomendo a leitura. Tags: autoload, [...]
April 18th, 2012 at 6:49 am
Readers of this article will probably be interested in this article
http://philsturgeon.co.uk/blog/2012/03/packages-the-way-forward-for-php