|
Autoloading ObjectsMany developers writing object-oriented applications create one PHP source file per-class definition. One of the biggest annoyances is having to write a long list of needed includes at the beginning of each script (one for each class). In PHP 5, this is no longer necessary. You may define an __autoload function which is automatically called in case you are trying to use a class which hasn't been defined yet. By calling this function the scripting engine is given a last chance to load the class before PHP fails with an error.
Note:
Exceptions thrown in __autoload function cannot be caught in the catch block and results in a fatal error.
Note:
Autoloading is not available if using PHP in CLI interactive mode. Example 10.7. Autoload example
This example attempts to load the classes <?php Code Examples / Notes » language.oop5.autoloadscott
__autoload() seems to work when saving objects as session variables as well: classLoader.php <?php function __autoload($className) { require_once("$className.php"); } ?> testClass.php <?php class testClass { function __construct($propValue) { $this->prop1 = $propValue; } function showProp() { return $this->prop1; } } ?> page1.php <?php require_once('classLoader.php'); session_start(); $_SESSION['testObj'] = new testClass('foo'); echo '<a href="page2.php">Go to page 2</a>'; ?> page2.php <?php require_once('classLoader.php'); session_start(); echo $_SESSION['testObj']->showProp(); // displays foo ?> Works with multiple session objects as well. Tested on a Win2k/IIS machine. me
You can enable this behaviour for undefined classes while unserializing objects by setting the .ini-variable 'unserialize_callback_func' to '__autoload'.
kencomer
Yet another class/interface __autoload function. Includes an example usage of the SPL DirectoryIterator class, a settable case-ignore flag, and support for multiple file name patterns to allow easy integration from multiple sources. <?php /** * __autoload * * @author Ken Comer * @copyright released into public domain 2005 Ken Comer */ define('IGNORE_CASE',true); // comment out the define() of IGNORE_CASE to be // case-sensitive. I like to ignore case so that I can // use UPPERCASE for the test versions of the file. /** * autoloads classes and interfaces for PHP5 * * @author Ken Comer */ function __autoload($class_name) { // This will be set the first time through. // Put your default values in the place indicated // below so as to eliminate possible duplicates // in the .ini include_path static $possible_path = NULL; // Leave this as NULL. // List here whatever formats you use for your // file names. Note that, if you autoload // a class that implements a non-loaded interface, // you will also need to autoload that interface. static $permitted_formats = array( "&CLASS.class.inc" ,"&CLASS.class.inc.php" ,"&CLASS.class.inc.php5" ,"class.&CLASS.inc" ,"class.&CLASS.inc.php" ,"class.&CLASS.inc.php5" ,"&CLASS.interface.inc" ,"&CLASS.interface.inc.php" ,"&CLASS.interface.inc.php5" ,"i&CLASS.interface.inc" ,"i&CLASS.interface.inc.php" ,"i&CLASS.interface.inc.php5" ); // Put the &CLASS wherever the $class_name // might appear // Only executed the first time __autoload is called if (NULL===$possible_path): // These are the default paths for this application $possible_path = array_flip(array( "." ,".." ,"../include" ,"/public_html/php/include" )); // Customize this yourself, but leave the // array_flip alone. We will use this // to get rid of duplicate entries from the // include_path .ini list. // Merge the flipped arrays to get rid of duplicate // "keys" (which are really the valid include // paths) then strip out the keys leaving only // uniques. This is marginally faster than // using array_combine and array_unique and // much more elegant. Okay, it's weird, too. $possible_path = array_keys(array_merge($possible_path, array_flip(explode(ini_get("include_path"),";")))); endif; /* static $possible_path initialization */ $possibility = str_replace("&CLASS",$class_name,$permitted_formats); foreach ( $possible_path as $directory ) { if (!file_exists($directory) or !is_dir($directory)) { continue; } $file_to_check = new DirectoryIterator($directory); foreach ( $file_to_check as $file ) { // ignore directories and files that do not contain // $class_name if ( !$file->isDir() and ( defined(IGNORE_CASE) && TRUE===IGNORE_CASE ) ? stripos($file->getFileName(),$class_name) : strpos($file->getFileName(),$class_name) ) : // class_name was included, now compare against // all permitted file name patterns foreach ( $possibility as $compare ): if ((defined(IGNORE_CASE) && TRUE===IGNORE_CASE ) ? !strcasecmp($compare,$file->getFileName()) : $compare===$file->getFileName() ) { // by using $compare, you will get a qualified // file name include_once($compare); return TRUE; } endforeach; /* $possibility */ endif; } /* foreach $file_to_check */ } } ?> quetzalcoatl
While __autoloading is a nice feature, one can stumble upon small problem with it. Imagine: file A.php: <? function __autoload($cname) { require_once "include/$cname.php";} } B::testC(); ?> file B.php: <? function __autoload($cname) { require_once "include/$cname.php";} } class B { public static function test() { C::testMe(); } } ?> file C.php: <? class C { public static function testMe() { print("OK!"); } } ?> Now, running file A.php will result in an error.. because from A's point of view, function __autoload is declared twice - once in A.php, and in required_once'd B.php, too! [Not mentioning that C.php and many many many other required or included files could use it too :) ] So, of course we can remove the duplicate __autoload functions from B.php and any other required files. Thus, we either disallow usage of B.php independently (and other required-able/include-able files!) , as without the __autoload it can not work -- or we have to manually require_once its dependecies, thus making manually the job of __autoload. Irritating, isn't it? A simple trick can help us here. Every file you would place an __autoload function, place instead something like: if(!function_exists("__autoload")) { //your __autoload declaration, for example: function __autoload($cname) { require_once "include/$cname.php"; } } When I first tried it, I was really surprised that you can control functions' declaration with simple IF, without any evals or other tricks. Well.. it's probably a basic "trick", but I didn't notice it anywhere. Feel free to remove it if it was already posted somewhere:) alexey
While using an "autoloading" method you should pay attention to variables scope. Because of new file will be included INSIDE of magic function __autoload - all of declared in such file global scope variables will be only available within this function and nowhere else. This will cause strange behaviour in some cases. For example: file bar.class.php: <?php $somedata = 'Some data'; /* global scope in common way */ class bar { function __construct() { global $somedata; /* reference to global scope variable */ if ( isset($somedata) ) { var_dump($somedata); } else { die('No data!'); } } } ?> Attempt to load this file in common way: <?php require 'bar.class.php'; $foo = new bar(); ?> this will output (as expected): string(9) "Some data" But in case of __autoload: <?php function __autoload($classname) { require $classname . '.class.php'; } $foo = new bar(); ?> you could expect that this script will return the same but no, it will return "No data!", because defenition of $somedata after requiring treats as local within user-defined function __autoload(). erikzoltan nospam
When using __autoload you'll have one class in each file. I have developed the habit of placing a UML class diagram at the top of each file. It's extremely helpful when you start to work with a large number of classes, especially if you're part of a team. And it's easy to do. <?php /* |--------------------------------| | MyExample : SomethingElse | |--------------------------------| | +$FirstProperty | | +$SecondProperty: array | | -$ThirdProperty | |--------------------------------| | +new MyExample() | | #ClassMethod($a,$b,$c): number | | +DoSomething() | | -SomethingElse() | |--------------------------------| */ class MyExample extends SomethingElse { public $FirstProperty; public $SecondProperty = array(); private $ThirdProperty; public __constructor() { } public static function ClassMethod($a, $b, $c) { return $a+$b+$c; } public function DoSomething() { } private function SomethingElse() { } } ?> Of course you have to maintain the UML comments along with your classes. (I'm planning to use reflection to automate this process in the future so that issue will soon disappear.) kai
To: gomasj at gmail dot com I strongly disagree in that "no well organized programmer will need to use it". The method reduces the overhead when loading unneeded classes while encouraging the use of well organized class libraries. Assume you have a class needing an undetermined number of other classes to operate properly - let's say an XML-parser that parses a document into an object tree (one class per element is a typical procedure). A common behavior was to load all possibly needed classes in advance - regardless if they would be used ever. That would be a performance deasaster for the above case. Another possibility was to load the particular class when needed - providing a path to the file containing the class declaration. Since relative path's in PHP are calculated relative to the script handling the client request, they're some kind of complex to manage in huge projects, where the script can be called from various locations. If you're willing to move a class's declaration file to another location (directory) you have to update each reference to that file. Lot's of well organized programmers (so are you), wrote their own mechanisms so solve these problems. But you always had to make at least one call to load the class file each time it might possibly be referenced for the first time in code execution - that are: - new - extends - implements - Type hints - static reference (class::xxx) - deserialization of sessions __autoload() simply provides an event handler to centralize the loading of a class's declaration only when it's needed for the first time. Regardless where or by what the request occurred. So it keeps your code clean, fast and allows you to reorganize your class-library as you like without modifying any of your source files (except the autoload-function itself). We use the autoload feature in a 200,000 lines php-project as follows: The 'ClassManager' maintains a cache file containing the file paths to each class-declaration. Whenever an unknown class (not present in the cache, yet) is requested, it searches the directories for a file containing the desired class declaration and adds it to the cache for the next time. This method allows us to modify our class-library at will. We can add, move or remove classes without adjusting a single line of code. When our software starts up, the ClassManager has to handle about 6,000 class requests - without having to worry about performance reasons. I am developer and project manager in the project mentioned above (a web based cms) - so I'd call myself well organized. Anyway, thanks for your opinion przemekg_
This is a little more usefull code (modified from nhartkamp at eljakim dot N0SP4M dot nl) <?php function __autoload ($class_name) { $file = 'system/objects/' . $class_name . '.inc.php'; if (!file_exists ($file)) { return eval ("class $class_name {" . " function $class_name ($a=0, $b=0, $c=0, $d=0, $e=0, $f=0, $g=0, $h=0, $i=0) {" . " throw new Exception ();" . " }" . "}"); } require_once ($file); } ?> Adding some arguments to the constructor will prevent errors about argument count. rico
This autoload function searches for the class Location before requiring it. So there's no need of putting the classes all in one folder. Requirements: - the subfolders must be at least 3 letters long - the filenames must be in the form CLASSNAME.class.php Note: - in this example the main class folder is 'lib' define('ROOT_DIR', dirname(__FILE__).'/'); function __autoload($className) { $folder=classFolder($className); if($folder) require_once($folder.'/'.$className.'.class.php'); } function classFolder($className,$folder='lib') { $dir=dir(ROOT_DIR.$folder); if($folder=='lib' && file_exists(ROOT_DIR.$folder.'/'.$className.'.class.php')) return $folder; else { while (false!==($entry=$dir->read())) { $checkFolder=$folder.'/'.$entry; if(strlen($entry)>2) { if(is_dir(ROOT_DIR.$checkFolder)) { if(file_exists(ROOT_DIR.$checkFolder.'/'.$className.'.class.php')) return $checkFolder; else { $subFolder=classFolder($className,$checkFolder); if($subFolder) return $subFolder; } } } } } $dir->close(); return 0; } nhartkamp
The following might provide a good work-around for throwing exceptions from the __autoload function when a file containing the correct class doesn't exists. function __autoload ($class_name) { $file = 'system/objects/' . $class_name . '.inc.php'; if (!file_exists ($file)) { return eval ("class $class_name {" . " function $class_name () {" . " throw new Exception ();" . " }" . "}"); } require_once ($file); } Cheers, Nolan petyo architect . bg
The following function may be useful if you want to simulate namespaces and autoloading behavior: define ("CLASS_ROOT", '/classes/'); function __autoload ($className) { require_once CLASS_ROOT.str_replace('_', '/', $className).'.class.php'; } Then you will just have to use the folder structure and name the classes accordingly. If you want to have a class named Page, which will be in the pseudo namespace System.Web.UI, create a directory named System in /classes, then create Web, then UI, then name the class System_Web_UI_Page. Kind of long to type if you don't have autocomplete, but at least you will not have to manage the loading of all the classes' definitions. php
The autoload-feature allows to add the behavior of static constructors (like in C#). Static constructors should be called on the first occurence of a class reference - typically a 'new' operator or a static call to a class's operation. They can be used used to initialize complex static properties. And here is an easy and save way how it can be done: Content of MyClass.class.php5: <?php // demo class persisting of a static and a dynamic constructor class MyClass { // static constructor operation public static function _construct() { // just say hello echo '<div>static constructor</div>'; } // default dynamic constructor operation public function __construct() { // just say hello echo '<div>dynamic constructor</div>'; } } ?> Content of index.php5: <?php // declare handler for any unknown class request function __autoload($aClassName) { // load the class require_once ($aClassName . '.class.php5'); // create a reference to the static constructor's operation $staticConstructorReference = array($aClassName, '_construct'); // if the static constructor was declared properly... if (is_callable($staticConstructorReference)) { // call the static constructor call_user_func($staticConstructorReference); } } // create an example object to see both constructors being executed $article = new MyObject(); ?> christopher dot klein
Sometimes you could get in trouble, if you have already written a management-class for an object, but you still don not have designed the object. For example you have created a class called Management_FileObject: <?php class Management_FileObject { private static $objFile; public static function getInstance() { if (!is_object(self::$objFile)) { self::$objFile = new FileObject(); } return self::$objFile; } public static function writeData($data) { self::getInstance()->setData($data); } public static function readData() { return self::getInstance()->getData(); } } ?> The object FileObject is still not defined (why-ever). Now we can use a method, which I want to name "Extreme Prototyping". Redefine the __autoload()-function: <?php function __autoload($className) { // Define more search-pathes here $arrPath = array('../classes/'); for ($i = 0, $m = sizeof($arrPath); $i < $m; $i++) { $phpFile = $arrPath[$i].$className.".php5"; if (file_exists($phpFile)) { include_once($phpFile); return true; } } $str = 'class '.$className.' { private static $objInstances = array(); public function __construct() { self::$objInstances[] = $this; } private $objVals = array(); public function __call($m, $a) { if (preg_match("/^(set|get)(.*)/",$m,$arrRet)) { if ($arrRet[1] == "get") { if ($this->objVals[$arrRet[2]]) { return $this->objVals[$arrRet[2]]; } } else { $this->objVals[$arrRet[2]] = $a[0]; return true; } } } public function dumpVar() { var_dump($this); } public static function dumpObjects() { var_dump(self::$objInstances); } }'; eval($str); } ?> Now you can test the code above with this lines: <?php $testData = "Extreme Prototyping success"; Management_FileObject::writeData($testData); if ($testData != ($returnData = Management_FileObject::readData())) { print "Extreme Prototyping falied."; } else { print "Got ".$returnData; } ?> I hope, you find this usefull. Best regards, Christopher Klein shurikk
somebody might find this "quick and dirty" example useful (see the code below). BTW, is there a magic metod that tells you what the class file name is? If none, perhaps a feature request? What this example about: store objects in the session and restore them without worry of what external files to be "required" (solves the problem when you get incomplete PHP object, when your class is not declared prior unserialize()) PHP5, but easy to adapt for PHP4 --- file Session.php <?php session_start(); // don't do any comments, nothing special in this class IMO class Session { public static function has($name) { return isset($_SESSION[$name]); } public static function get($name) { if(Session::has($name)) { if(Session::has('__requireFiles')) { $requireFiles = unserialize($_SESSION['__requireFiles']); if(array_key_exists($name, $requireFiles)) { require_once($requireFiles[$name]); } } return unserialize($_SESSION[$name]); } return null; } public static function put($name, $value) { if(is_object($value)) { if(method_exists($value, 'getClassFile')) { if(!($requireFiles = Session::get('__requireFiles'))) $requireFiles = array(); $requireFiles[$name] = $value->getClassFile(); Session::put('__requireFiles', $requireFiles); } } $_SESSION[$name] = serialize($value); } } ?> --- end Session.php --- file a.php <?php include('Session.php'); // an object, all as usuall, but with one magic method // getClassFile(), you can call it whatever you like class A { public $a; function __construct($a) { $this->a = $a; } // our kinda magic method public function getClassFile() { return __FILE__; } } $a = new A('test'); Session::put('a', $a); } ?> --- end a.php --- file b.php <?php include('Session.php'); // everybody's happy :) $a = Session::get('a'); ?> --- end b.php knoopx
Quick and easiest way to be able to throw exceptions on __autoload() method: <?php function __autoload($class_name) { find_file($class_name); //your own routine to locate the class file if (!class_exists($class_name, false)) { eval("class $class_name {}"); throw new Exception('Class not found'); } } ?> shurikk
one more thing about my previous example/comment (there is a bug :) ) --- file a.php should be like this <?php class A { public $a; function __construct($a) { $this->a = $a; } // our kinda magic method public function getClassFile() { return __FILE__; } } ?> --- end a.php one more file to test the example --- b1.php <?php include('Session.php'); $a = new A(); $a = Session::put('a', $a); // go to b.php now ?> --- end b1.php open b1.php, and then b.php in your browser. in the Session class you might want to implement remove() method. thanks --alex emcmanus
Note: if you're experiencing unexpected "failed opening required 'filename.php' (include..." errors: If you placed your autoload function in an external file which you're requiring at the head of every script, be cautious of some odd behavior regarding PHP's idea of the current working directory. I ran into some unexpected path issues when my include file was placed in a subdirectory directory. The solution to my problems was to make sure that the autoload script being included is in the same directory as the calling script. david dot thalmann
Note to Ricos posting: A lot of useless Coding. However, I improved the code, so now it will be able to find any folders ("." and ".." will not being tested... oO) and search as deep as possible. Now it will find CLASS_DIR/foo/bar.class.php also like CLASS_DIR/foo/bar/baz/buz/fii/and/so/on/class.php Warning: This code will check ALL dirs who're "deeper" / "lower" than the class dir, so prevent deeply hidden files (or use just a few folders). Improved Version: <?php // change this, if this code isn't "higher" than ALL classfiles define("CLASS_DIR", dirname(__FILE__)); /** * autoload classes (no need to include them one by one) * * @uses classFolder() * @param $className string */ function __autoload($className) { $folder = classFolder($className); if($folder) require_once($folder.$className.".class.php"); } /** * search for folders and subfolders with classes * * @param $className string * @param $sub string[optional] * @return string */ function classFolder($className, $sub = "/") { $dir = dir(CLASS_DIR.$sub); if(file_exists(CLASS_DIR.$sub.$className.".class.php")) return CLASS_DIR.$sub; while(false !== ($folder = $dir->read())) { if($folder != "." && $folder != "..") { if(is_dir(CLASS_DIR.$sub.$folder)) { $subFolder = classFolder($className, $sub.$folder."/"); if($subFolder) return $subFolder; } } } $dir->close(); return false; } ?> ernest
Note that it is not possible to catch an exception thrown from within __autoload. See also bug report #26193 (http://bugs.php.net/bug.php?id=26193) - this states "Won't fix", so read it before you holler. andy bravery
kencomer, I think there is a problem with your function below in that if strpos() or stripos() locate your class name at the beginning of the filename string then they return a position 0 which gets interpretted as a boolean FALSE. For me this meant my file was never found.
krzysztof 'chanibal' bociurko
It should be noted that after you throw the exception using the fake evaled class, __autoload() will NOT run anymore with this class, because of the already existing fake one, so the second time you try new UndefinedClass() there will be NO exception, however if you throw an Exception in the __construct() of the fake class it will be cought. Example code: <?php function __autoload($class) { if(file_exists($f="{$class}.php")) require_once($f); else { $errorText="Could not find class {$class}, searched in {$f}"; eval("class {$class} { " ."public function __construct() {throw new ClassNotFoundException('$errorText');}" .'};'); throw new ClassNotFoundException($errorText); } } ?> Unfortunately this still doesn't work 100% correct, because it relies on class initialization, therefore static sets/gets/calls will produce a fatal error if the unexisting class was being tried to be accessed before, as in: <?php try { new Undefined(); } catch (ClassNotFoundException $e) { // the problematic fake class Undefined should exist by now... } Undefined::$staticVar='some value'; print Undefined::$staticVar; print Undefined::staticMethod(); print Undefined::SOME_CONST; ?> Overloading the __set(), __get() or __call() won't help, they don't work with statics. smith
It seems that __autoload() works both for classes and interfaces.
unspammable-iain
in response to the post by UNSPAMMABLEkai at kaiundina dot de, i have another interesting implementation of the __autoload() system. i have all my classes in their own file, where ThisClassName would be in the file this_class_name.class.php (Ruby inspired :-) i really like having all class dependencies at the top of the class file, because you can tell at a glance what packages/classes a particular class uses/depends on, but this meant big loading times for huge class heirarchies, even if all those classes were not being used. so i created my own require function that uses a serialised array of include paths, and then an __autoload() function that will get the required included path when it is needed _only_. this also allows for nice and liberal directory structures. Here is the file with all the implementation in it: <? @$contents = file_get_contents('managed_code/includes.php'); if($contents) $include_paths = unserialize($contents); else $include_paths = array(); function __autoload($class) { if($path = rsIncludePath($class)) require_once($path); else { echo('You need to call rsRequire() before using a class: '.$class); debug_print_backtrace(); } } function rsIncludePath($class) { global $include_paths; $ret = ''; if(isset($include_paths[$class])) $ret = $include_paths[$class]; return $ret; } function rsRequire($class,$required = true) { global $include_paths; $classname = rsClassName($class); $path = preg_replace('/\./','/',rsClasstoFile($class)); $path .= '.class.php'; if((!file_exists('packages/'.$path))) { echo('couldn\'t load package: '.$class.'<br /><br /> '); if($required) debug_print_backtrace(); } else { $path = 'packages/'.$path; if(isset($include_paths[$classname])) { if($include_paths[$classname]!=$path) { $include_paths[$classname] = $path; rsPersist(); } } else { $include_paths[$classname] = $path; rsPersist(); } } } function rsClassName($class) { $split = preg_split('/\./',$class); $class_name = $split[count($split)-1]; return $class_name; } function rsPersist() { global $include_paths; $data = serialize($include_paths); file_put_contents('managed_code/includes.php',$data); } ?> Now here is a class file: <? rsRequire('package.name.SomeBaseClass'); rsRequire('package.name.MightNotBeUsed'); class ThisClass extends SomeBaseClass { /**ALL MY CODE***/ } ?> the class file for MightNotBeUsed would only be included if it was actually used in the class code. Also, notice that the above method is quite efficient because it only unserialises the array of include paths once, and will only reserialise the array in the case that it is a new include path, or the include path has changed. Also notice that this is 'session safe', meaning if you register a session object and then your application dies, you will not get class non-existing errors the next time you run it because the include paths are serialised as they are added. Enjoy! gonix
in response to alexey at renatasystems dot org: You may add ``global $somedata;`` before ``$somedata = 'Some data';`` and it should work as expected. file bar.class.php: <?php global $somedata; $somedata = 'Some data'; /* global scope in common way */ class bar { function __construct() { global $somedata; /* reference to global scope variable */ if ( isset($somedata) ) { var_dump($somedata); } else { die('No data!'); } } } ?> 'common way': <?php require 'bar.class.php'; $foo = new bar(); ?> '__autoload way': <?php function __autoload($classname) { require $classname . '.class.php'; } $foo = new bar(); ?> Both 'comon way' and '__autoload way' should give same result: string(9) "Some data" dave60 /at/ gmail /dot/ com
In reply to quetzalcoatl: Generally, I would advise for each class to have it's own file, and hold nothing besides that class. Just define __autoload() in a/the infrastructure file -- a/the file that does the behavioral logic, and there should be no need to redefine it in a class' file. andrew dot delete dot cornes
If you'd like '__autoload()' to support multiple class folders, each containing multiple class files (one per class), you may want to try something like this (file '__autoload.php'): <?php define('CLASS_FILENAME_SUFFIX', '.class.php'); function __autoload($className) { $__autoloadAbsolutePath = dirname(__FILE__); // 'pathStart' is your web application root folder. // (This may or may not be where '__autoload.php' // resides; let's assume here that it resides one // level 'below' the web app root.) $pathStart = $__autoloadAbsolutePath . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; // 'classPath' is a list of class folders to look in. // (In this example, there's just one: 'classlibs/lib1'. // To add more, simply append them; start with // 'PATH_SEPARATOR . $pathStart .', and off you go...) $classPath = PATH_SEPARATOR . $pathStart . 'classlibs' . DIRECTORY_SEPARATOR . 'lib1'; // Add list of class folders to 'include_path' for the // forthcoming 'require()' (or similar directive). $oldIncludePath = get_include_path(); set_include_path($oldIncludePath . $classPath); require_once($className . CLASS_FILENAME_SUFFIX); // Reinstate initial 'include_path'. set_include_path($oldIncludePath); } ?> As your web application develops, new paths containing class files can be added into the '$classPath' variable within '__autoload()'. If hard-coding the '$classPath' variable isn't to your taste, you could arrange for its value to come from 'outside' in whatever way you like. Any comments gratefully received. rlee0001
If you would like __autoload to throw an exception when the class cannot be loaded instead of causing a fatal error, consider this: <?php function __autoload ($className) { $fileName = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; $status = (@include_once $fileName); if ($status === false) { eval(sprintf('class %s {func' . 'tion __construct(){throw new Project_Exception_AutoLoad("%s");}}', $className, $className)); } } $pageController = 'Project_My_Class'; // "Project/My/Class.php" try { $obj = new $pageController(); } catch (Project_Exception_AutoLoad $e) { header('HTTP/1.0 404 Not Found'); printf('<h1>Not Found</h1> The requested page %s was not found on this server.<hr /><em>$id$</em>', $_SERVER['REQUEST_URI']); } ?> thomas dot revell
If you want to throw an exception if a class isn't defined yet, use class_exists (): <?php // See if the class is defined if (!class_exists ($className, false)) { throw new Exception ("Class $className is not defined."); } ?> The second parameter indicates whether or not the __autoload () function should be called before checking for the class's existence. sr
If you want to autoload functions you can do this with a little work-around: <?php class utilities { protected static $instance; protected function __construct() { } public function get() { if (isset(self::$instance)) { return self::$instance; } else { self::$instance = new utilities(); return self::$instance; } } public function __call($function, $arguments) { if (!function_exists($function)) { include_once("function.$function.inc"); } return call_user_func_array($function, $arguments); } } utilities::get()->someFunction(...); // the file 'function.someFunction.inc' should be somewhere in the include_path ?> Unfortunately __call() is only invoked when the method is accessed via -> and not via ::. p dot scheit
If you really wan't to use something like Exceptions, but you don't want to use eval (like me) I have a suggestion for you. This is "oldschool" Try and Catching by checking the return value. $cfgLine = $this->generateMapping($className,NULL,TRUE); if ($cfgLine instanceof Exception) { $e = $cfgLine; trigger_error('uncaught exception: '.$e->getMessage().' '.$e->getTraceAsString(), E_USER_ERROR); } you get the point... This looks like a usual uncaught Exception. It's a little bit tricky to check all return values from your Methods, but you can catch and throw something ... better than doing nothing chris corbyn chris
I'm sure this is needed by more than me. My objective was to allow __autoload() to be easily extended in complex systems/frameworks where specific libraries etc may need loading differently but you don't want to hard-code little adjustments into your working __autoload() to allow this to happen. Using a ServiceLocator object with some static methods and properties to allow loosely coupled locators to be attached to it you can swap/change and add to the functionality of your __autoload() at runtime. The core stuff: <?php /** * Defines the methods any actual locators must implement * @package ServiceLocator * @author Chris Corbyn */ interface Locator { /** * Inform of whether or not the given class can be found * @param string class * @return bool */ public function canLocate($class); /** * Get the path to the class * @param string class * @return string */ public function getPath($class); } /** * The main service locator. * Uses loosely coupled locators in order to operate * @package ServiceLocator * @author Chris Corbyn */ class ServiceLocator { /** * Contains any attached service locators * @var array Locator */ protected static $locators = array(); /** * Attach a new type of locator * @param object Locator * @param string key */ public static function attachLocator(Locator $locator, $key) { self::$locators[$key] = $locator; } /** * Remove a locator that's been added * @param string key * @return bool */ public static function dropLocator($key) { if (self::isActiveLocator($key)) { unset(self::$locators[$key]); return true; } else return false; } /** * Check if a locator is currently loaded * @param string key * @return bool */ public static function isActiveLocator($key) { return array_key_exists($key, self::$locators); } /** * Load in the required service by asking all service locators * @param string class */ public function load($class) { foreach (self::$locators as $key => $obj) { if ($obj->canLocate($class)) { require_once $obj->getPath($class); if (class_exists($class)) return; } } } } /** * PHPs default __autload * Grabs an instance of ServiceLocator then runs it * @package ServiceLocator * @author Chris Corbyn * @param string class */ function __autoload($class) { $locator = new ServiceLocator(); $locator->load($class); } ?> An example Use Case: <?php require 'ServiceLocator.php'; //Define some sort of service locator to attach... class PearLocator implements Locator { protected $base = '.'; public function __construct($directory='.') { $this->base = (string) $directory; } public function canLocate($class) { $path = $this->getPath($class); if (file_exists($path)) return true; else return false; } public function getPath($class) { return $this->base . '/' . str_replace('_', '/', $class) . '.php'; } } // ... attach it ... ServiceLocator::attachLocator(new PearLocator(), 'PEAR'); // ... and code away.... $foo = new Foo_Test(); ?> klaus schneider
I just stumbled over one quite nice solution to the __autoload-exception problem. It allows for any kind of exception to be thrown inside __autoload(). It appears one has to define the requested class (using "eval", which is not nice but inevitable here) and after that can simply throw an exception (and catch it if so desired): <?php function __autoload($className) { // Do your stuff to load a class here, set $ok if everything went fine. if (! $ok) { eval("class $className{};"); throw new Exception('My message'); } // if } try { UndefinedClass::undefinedFunction(); } catch (Exception $ex) { echo $ex->getMessage(); } // try/catch ?> Output: "My Message". :-) sr
I hope this helps some people: <?php function __autoload($name) { $class = $interface = false; // interface first if (include_exists("interface.$name.inc")) { include_once("interface.$name.inc"); $interface = true; } // class later if (include_exists("class.$name.inc")) { include_once("class.$name.inc"); $class = true; } // catch error if ($class === false && $interface === false) { trigger_error("Failed to include class/interface $name", E_USER_WARNING); return false; } return true; } function include_exists($file) { static $include_dirs = null; static $include_path = null; // set include_dirs if (is_null($include_dirs) || get_include_path() !== $include_path) { $include_path = get_include_path(); foreach (split(PATH_SEPARATOR, $include_path) as $include_dir) { if (substr($include_dir, -1) != '/') { $include_dir .= '/'; } $include_dirs[] = $include_dir; } } if (substr($file, 0, 1) == '/') { //absolute filepath - what about file:///? return (file_exists($file)); } if ((substr($file, 0, 7) == 'http://' || substr($file, 0, 6) == 'ftp://') && ini_get('allow_url_fopen')) { return true; } foreach ($include_dirs as $include_dir) { if (file_exists($include_dir.$file)) { return true; } } return false; } ?> torch
I have just noticed that __autoload function is also called when extending classes. For example: <?php // This is file called class.test1.php class test1 { /* class body */ } ?> <?php // This is file class.test2.php class test2 extends test1 { /* class body */ } ?> <?php // This is the file that contains __autoload function __autoload($class) { require_once($class . ".php"); } ?> The file "class.test1.php" will be reqire'd even though it is not implicitly called by new keyword. info
Hey there, I've published c^2, an autoloading-system with self-modificating cache and directory crawler under www.tobias-demuth.de. License is LGPL - just include it and don't think about class-inclusion anymore! kind regards Tobias sandrejev
Here is the most complete version of __autoload exception i guess. The best thing is that it can throw any exception plus the exception is fully functional. <?php class AutoloadException extends Exception { } class AutoloadExceptionRetranslator extends Exception { public function __construct($serializedException) { throw unserialize($serializedException); } } function __autoload($classname) { if(!file_exists($classname)) { $autoloadException = serialize(new AutoloadException("Class $classname could not be found")); return eval(" class $classname { function __construct(\$a=0, \$b=0, \$c=0, \$d=0, \$e=0, \$f=0, \$g=0, \$h=0, \$i=0) { throw new AutoloadExceptionRetranslator('$autoloadException'); } } "); } else { require_once $classname; } } try { $anyObject = new AnyNonExistantClass(); } catch (AutoloadException $e) { print_r($e->getTrace()); } ?> alexandre
Here is a simple version of __autoload for creating custom classes on the fly, extending pre-defined (using a constant) base classes: <?php define('CUSTOM_CLASS_TYPES', 'Application|Database|XML'); function __autoload($req) { if (preg_match('#(.*)('.CUSTOM_CLASS_TYPES.'){1}$#', $req , $fragment)) eval('class '.$req.' extends base'.$fragment[2].' { public $_name = \''.$fragment[1].'\'; public $_type = \''.$fragment[2].'\'; } '); } /* Base Classes */ class baseApplication {} class baseDatabase {} class baseXML{} /* Calling a custom Class */ $foo = new projectApplication; print_r($foo); ?> el lobo
function autoloadwrapper($parameter='',$register=false) { static $function='default_function'; if( $register ){ $function=$parameter; return true; } if( ! is_array($parameter) ){ $param[]=$parameter; }else{ $param&=$parameter; } if( function_exists( $function ) ){ return @call_user_func_array( $function,$param ); }else{ $methode=explode("::",$function); return @call_user_func_array( $methode,$param ); } } function __autoload($class_name) { return autoloadwrapper( $class_name ); } Simple hack to switch between different __autoload-functions. christian.reinecke
do not use is_subclass_of() in your __autoload() function to identify a class type and thereby its path (f.e exceptions). is_subclass_of() needs to know the class, but you want to check BEFORE you include the class.
peter dot gooman
Before you start using __autload, remember that it holds no scope/namespace. This means that if you are depending on third party applications and they have an autoload function defined and so do you, your application will error. To remedy this, everyone should look at the spl_autoload functions, eg: spl_autoload_register. This function allows more than one custom functions to be called through the default spl_autoload (default __autoload) handler. trini0
Be careful with using that eval() trick within __autoload(). If you use reflection in your code, the so called trick, *can* provide ill side effects. For example -> $reflection = new reflectionClass('some_class'); if (FALSE === $reflection->isSubClassOf('another_class')) { throw new Exception('Class "some_class" must extend base class "another_class"'); } If the real class "another_class" doesnt exist at the time, or "some_class" doesn't extend "another_class", with the reflection test, the so called eval() trick, creates a dummy "another_class", thereby making the reflection test useless... rojoca
Be careful when using eval (as always) in __autoload. The following: <?php echo 'Start->'; function __autoload($class) { eval('class ' . $class . ' {};'); } $class = 'Class1{}; echo "uh oh"; class Class2'; $obj = new $class; echo 'end'; ?> outputs: Start->uh oh You can use preg_replace to clean up $class to prevent executing abitrary code but in this case you won't be able to throw a catchable exception and your script will end with a fatal error. jami pekkanen
Autoloading can be abused to simulate C++-style templates: <?php function __autoload($className) { $names = explode('__', $className, 2); if(count($names) != 2) return; eval( "class $className extends {$names[0]} { public static function getTemplateValue() { return {$names[1]}; } }" ); } class Dummy {} echo Dummy__HelloWorld::getTemplateValue()."\n"; // HelloWorld $bye = new Dummy__ByeBye(); echo $bye->getTemplateValue()."\n"; // ByeBye ?> Also a great way to confuse anyone trying to read/maintain your code. andrea giammarchi
Another workaround for Exception problem (Klaus Schneider style) <?php define('CLASS_DIR', 'php/classes/'); function __autoload($name) { if($exists = !class_exists($name) && file_exists($class = CLASS_DIR.$name.'.class.php')) require $class; elseif(!$exists) { eval('class '.$name.' extends Exception {}'); throw new $name('[__autoload] this file doesn\'t exists: '.$class); } } try { new Undefined; } catch(Undefined $e) { echo $e->getMessage(); } // You should use generic Exception too catch(Exception $e) { echo $e->getMessage(); } ?> rquadling
An issue I've had with using the __autoload function is getting it into the application. You have to have the function included in every topmost script. This is a pain if the entire application is OOP and an "app" can be just a component of another "app". A solution I've found is to use php.ini's auto_prepend_file setting. Mine is set to ... auto_prepend_file = auto_loader.php The auto_loader.php script contains a single function. The __autoload() function. The include_dir path IS examined to find this file, so you can just put it with the rest of your includable files. A useful additional facility here is that you could log which classes are used by a script at runtime. Very useful if you have object factories and can't know the load at design time. Also, assigning the uncaught exception handler and the error handlers in this file means your entire site WILL have some global protection without you having to deal with it on a script by script basis. If you do not have access to the PHP.INI file, or you are running on a shared server, you may not be able to set this property. In those cases, you may be able to set the value using .htaccess. (NOTE: UNTESTED as I don't use Apache). <IfModule mod_php5.c> php_value auto_prepend_file "auto_loader.php" </IfModule> You COULD therefore have a different set of rules per subdomain (if you have multiple subdomains, say, live, test, beta, devel) or whatever takes your fancy. For more details on this see the "Description of core php.ini directives" (http://www.php.net/manual/en/ini.core.php) steven
An alternative to __autoload for PHP4. <?php /** * Set the variable **/ $string = ""; /** * Find pathnames matching the pattern **/ foreach( glob( $_SERVER['DOCUMENT_ROOT'] . "/classes/class.*.inc" ) as $filename ) { /** * Include and evaluates the specific file **/ require_once( $filename ); /** * Get the classnames **/ if( preg_match( "/class\\.(.+?)\\.inc/", $filename, $className ) ) { /** * Create a variable of the desired type with the 'new' operator **/ $string .= "$" . $className[1] . " = new " . ucfirst( $className[1] ) . ";"; } } /** * Evaluate the string as PHP code **/ eval( $string ); ?> dave
a static classlist can speed up the autoload in a arbitrary folder structure. each file holds one class with filename = classname + .php <?php define ("CLASS_PATH", "path to your classes"); function __autoload($className) { static $classes = null; if (!$classes) $classes = loadClasses(CLASS_PATH); require_once($classes[$className]); } function loadClasses($path) { $classes = array(); $h = @opendir($path); if ($h) while ($file = @readdir($h)) { if ($file=='.' || $file=='..') continue; if(is_dir($path.$file)) $classes = array_merge($classes, loadClasses($path.$file."/")); else $classes[array_shift(explode(".", $file))] = $path.$file; } return $classes; } ?> phpman
"Inspired" by unspammable-iain at iaindooley dot com, here is another approach to the dreaded PHP_INCOMPLETE_CLASS problem: <?php /** * Provides an include function which caches the path to a class in order to provide a better autoload functionality. * * While the Includer::doInclude() function takes a a string as an argument, which includes path information, __autoload() will * always get a "naked" classname. In order to find the according path for that class, we use our cache which essentially is a hash * of the form array(className => filePath). The inevitable constraint of that approach is obvious: we can't remember several * pathes for one and the same classname, meaning that it either has to be unique or you have to use several Includers for your * different, but equally named, classes. * * @author "Niels Ganser" <phpman@generic.depoll.de> */ class Includer { static private $instance; private $includePaths = array(); private $hasChanged = false; /** * We use Singeltons here, so please use this function to get an Includer instance. * * @return Includer An instance of Includer. And only one. */ public function instance() { if (empty(self::$instance)) self::$instance = new Includer(INCLUDE_PATH_CACHE); return self::$instance; } /** * @param string Path to the cache file. Obviously has to be read- and writable by the webserver user. */ private function __construct($cacheFile) { if (file_exists($cacheFile)) $this->includePaths = unserialize(file_get_contents($cacheFile)); } /** * Checks if the path to a class with the given name is cached. * * @param string $className Name of a class * @return boolean */ static public function isCached($className) { return (isset(self::instance()->includePaths[$className])) ? true : false; } /** * Returns the cached path for the given classname or null if there is nothing cached. * * @param string $className Name of a class * @return mixed A filepath (string) which we remember as leading to the given class or null if we have * no clue as to where to look for the class. */ static public function getPath($className) { return (self::isCached($className)) ? self::instance()->includePaths[$className] : null; } /** * Includes (or requires) a given class. * * @param string $class The path to our class in a package.className format. * I.e. "classes.money.AUD" would lead to "classes/money/AUD.php". * @param boolean $required If true, we require the file instead of just trying to include it. * @return boolean Return value of the require_once resp. include_once statement. */ static public function doInclude($class, $required = false) { $className = substr($class, strrpos($class, '.')+1); $path = str_replace('.', '/', $class) .'.php'; if (!isset(self::instance()->includePaths[$className]) or self::instance()->includePaths[$className] != $path) { self::instance()->includePaths[$className] = $path; self::instance()->hasChanged = true; } return ($required) ? require_once $path : include_once $path; } /** * Writes the cache to disk, if its contents have changed. */ public function __destruct() { if (self::instance()->hasChanged) file_put_contents(INCLUDE_PATH_CACHE, serialize(self::instance()->includePaths)); } } ?> It has the smell of a workaround but I think it is a rather clean one. And here is a simple __autoload function for use with Includer: <?php function __autoload($className) { if (Includer::isCached($className)) include_once Includer::getPath($className); else trigger_error('Cannot find '. $className, E_USER_ERROR); } ?> Of course you can extend that function to use other loading mechanisms if Includer::isCached() returns false. Also please note, that in the above implementation INCLUDE_PATH_CACHE has to the full path to a web server writable file. The inline documentation is shortened a bit. The complete file is on http://ng.nerdking.de/static/php/Includer.phps Cheers, Niels |
Change LanguageIntroduction The Basics Autoloading Objects Constructors and Destructors Visibility Scope Resolution Operator (::) Static Keyword Class Constants Class Abstraction Object Interfaces Overloading Object Iteration Patterns Magic Methods Final Keyword Object cloning Comparing objects Reflection Type Hinting Late Static Bindings |