Implementing class autoloading
When developing PHP using an object-oriented programming (OOP) approach, the recommendation is to place each class in its own file. The advantage of following this recommendation is the ease of long-term maintenance and improved readability. The disadvantage is that each class definition file must be included (that is, using include
or its variants). To address this issue, there is a mechanism built into the PHP language that will autoload any class that has not already been specifically included.
Getting ready
The minimum requirement for PHP autoloading is to define a global __autoload()
function. This is a magic function called automatically by the PHP engine when a class is requested but where said class has not been included. The name of the requested class will appear as a parameter when __autoload()
is invoked (assuming that you have defined it!). If you are using PHP namespaces, the full namespaced name of the class will be passed. Because __autoload()
is a function, it must be in the global namespace; however, there are limitations on its use. Accordingly, in this recipe, we will make use of the spl_autoload_register()
function, which gives us more flexibility.
How to do it...
- The class we will cover in this recipe is
Application\Autoload\Loader
. In order to take advantage of the relationship between PHP namespaces and autoloading, we name the fileLoader.php
and place it in the/path/to/cookbook/files/Application/Autoload
folder. - The first method we will present simply loads a file. We use
file_exists()
to check before runningrequire_once()
. The reason for this is that if the file is not found,require_once()
will generate a fatal error that cannot be caught using PHP 7's new error handling capabilities:protected static function loadFile($file) { if (file_exists($file)) { require_once $file; return TRUE; } return FALSE; }
- We can then test the return value of
loadFile()
in the calling program and loop through a list of alternate directories before throwing anException
if it's ultimately unable to load the file.Tip
You will notice that the methods and properties in this class are static. This gives us greater flexibility when registering the autoloading method, and also lets us treat the
Loader
class like a Singleton. - Next, we define the method that calls
loadFile()
and actually performs the logic to locate the file based on the namespaced classname. This method derives a filename by converting the PHP namespace separator\
into the directory separator appropriate for this server and appending.php
:public static function autoLoad($class) { $success = FALSE; $fn = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; foreach (self::$dirs as $start) { $file = $start . DIRECTORY_SEPARATOR . $fn; if (self::loadFile($file)) { $success = TRUE; break; } } if (!$success) { if (!self::loadFile(__DIR__ . DIRECTORY_SEPARATOR . $fn)) { throw new \Exception( self::UNABLE_TO_LOAD . ' ' . $class); } } return $success; }
- Next, the method loops through an array of directories we call
self::$dirs
, using each directory as a starting point for the derived filename. If not successful, as a last resort, the method attempts to load the file from the current directory. If even that is not successful, anException
is thrown. - Next, we need a method that can add more directories to our list of directories to test. Notice that if the value provided is an array,
array_merge()
is used. Otherwise, we simply add the directory string to theself::$dirs
array:public static function addDirs($dirs) { if (is_array($dirs)) { self::$dirs = array_merge(self::$dirs, $dirs); } else { self::$dirs[] = $dirs; } }
- Then, we come to the most important part; we need to register our
autoload()
method as a Standard PHP Library (SPL) autoloader. This is accomplished usingspl_autoload_register()
with theinit()
method:public static function init($dirs = array()) { if ($dirs) { self::addDirs($dirs); } if (self::$registered == 0) { spl_autoload_register(__CLASS__ . '::autoload'); self::$registered++; } }
- At this point, we can define
__construct()
, which callsself::init($dirs)
. This allows us to also create an instance ofLoader
if desired:public function __construct($dirs = array()) { self::init($dirs); }
How it works...
In order to use the autoloader class that we just defined, you will need to require Loader.php
. If your namespace files are located in a directory other than the current one, you should also run Loader::init()
and supply additional directory paths.
In order to make sure the autoloader works, we'll also need a test class. Here is a definition of /path/to/cookbook/files/Application/Test/TestClass.php
:
<?php namespace Application\Test; class TestClass { public function getTest() { return __METHOD__; } }
Now create a sample chap_01_autoload_test.php
code file to test the autoloader:
<?php require __DIR__ . '/../Application/Autoload/Loader.php'; Application\Autoload\Loader::init(__DIR__ . '/..');
Next, get an instance of a class that has not already been loaded:
$test = new Application\Test\TestClass(); echo $test->getTest();
Finally, try to get a fake
class that does not exist. Note that this will throw an error:
$fake = new Application\Test\FakeClass(); echo $fake->getTest();