- Laravel, Composer, and the State of Autoloading
- Composer Autoloader Features: Part 1
- Composer Autoloader Features: Part 2
- Registering Laravel’s Autoloaders
- Laravel’s Framework Autoloader
- Laravel Autoloader Interactions
- Laravel 5 Autoloader
PHP, the language, leaves a lot to be desired when it comes to sharing and organizing your code in a project. This problem goes back to PHP’s roots, and the choice not to include a python or ruby like module system. Over the years the PHP core team has added features to the language that can help developers crete their own code management systems, but the language itself has remained neutral.
One of those systems is the autoloader, and today we’ll be starting a series talking about the state of PHP’s autoloader culture, Composer, the PSR standards, and everything a developer new to Laravel needs to know about all of them.
Autoloading Basics
The basic idea behind autoloading is
- A programmer instantiates an undefined class
- Before PHP dies with a fatal error, it automatically calls a user defined function/callback, passing the class name as a parameter
- This user defined function implements logic to automatically
include
orrequire
the class definition - After running the autoloader, PHP instantiates the class
When an autoloader works well, it means a PHP developer can just start instantiating the classes they need, and the system will automatically include
or require
them behind the scenes.
There’s two different ways a PHP developer can create an autoloader. The first is to define a special global function named __autoload
function __autoload($class_name)
{
//logic to load a class definition here
}
As a systems developer, this creates a few problems. The primary one is you can only define a single __autoload
function. This means it’s impossible for two systems developers to have custom autoloaders unless they collaborate and agree on a single __autoload
function, and then synchronize versions of their frameworks. Because of this, __autoload
isn’t often used outside of private, single project frameworks.
To solve this problem, PHP also offers the spl_register_autoloader
function. This function allows you to register multiple PHP callbacks as autoloaders.
//using an anonymous function
spl_autoload_register(function($class_name){
//login to load a class definition here
});
//using a global function
function myCustomAutoloadDunction()
{
//login to load a class definition here
}
spl_autoload_register('myCustomAutoloadDunction');
//using a static method on a class
class MyCustomAutoloader
{
static public function myLoader($class_name)
{
//login to load a class definition here
}
}
spl_autoload_register(array('MyCustomAutoloader','myLoader'));
//using an instance method on an object
class MyCustomAutoloader
{
public function myLoader($class_name)
{
}
}
$object = new MyCustomAutoloader;
spl_autoload_register(array($object,'myLoader'));
Then, when a program instantiates an undefined class, PHP will call each registered function until it finds a definition. This way code from multiple frameworks can coexist, each with their own autoloaders.
Autoloading Standards
While the ability to registered multiple autoloaders has helped move PHP along, this approach is not without its own problems. A poorly behaving autoloader can still create conflicts for the other autoloaders. For example, Magento 1’s Varien_Autoloader
doesn’t check if its class files exist before calling include
, which means an error message when the class file isn’t found. If you want to include a second autoloader with Magento 1, you need to jigger the autoloading order with spl_autoload_functions
and spl_autoload_unregister
, which is far from ideal.
In recent years the PHP Framework Interoperability Group (PHP FIG) have developed two autoloader standards — the idea being that framework developers all agree to use these standards, and autoloader conflicts become a thing of the past.
While framework developers have been slow to discard their old autoloaders, the PSR-0/PSR-4 effort is buoyed by the PHP Composer project. Composer automatically includes PSR-0 and PSR-4 implementations for any package. A framework developer might meow meow meow about using the PSR standards in their own framework, but if they want to tap into the power of Composer distribution it means the PSRs come along on the ride for free.
Laravel’s Autoloading
All of this means that autoloading culture is in a state of flux. When everything works, it’s fantastic. A developer just instantiates a class and they’re off to the races. However, framework developers are hesitant to simply drop their old autoloaders for fear of confusing their community of end-user-programmers, many of whom might not even know the autoloader exists.
This means most PHP frameworks, even if they use Composer, have a number of autoloaders defined. Laravel presents an almost perfect case study of this. A stock instance of Laravel 4.2 has four autoloaders defined. You can see this for yourself by using the spl_autoload_functions
function, but we’ll save you the trouble. Laravel’s four autoloaders are
- Illuminate\Foundation\AliasLoader::load
- Composer\Autoload\ClassLoader::loadClass
- Swift::autoload
- Illuminate\Support\ClassLoader::load
Two autoloaders come from the Laravel framework itself, another is Composer’s standard autoloader, and the fourth is a legacy autoloader included by a PHP library from the dawn of autoloaders.
The remainder of this tutorial will examine the specific implementation of each of these autoloaders. Future articles in the series will cover how they’re added to the system, how they interact with each other, and the additional steps needed to take advantage of the special Composer autoloader.
Laravel Class Loader
The Illuminate\Support\ClassLoader::load
method is Laravel’s main autoloader. Its implementation is relatively straight forward.
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
public static function load($class)
{
$class = static::normalizeClass($class);
foreach (static::$directories as $directory)
{
if (file_exists($path = $directory.DIRECTORY_SEPARATOR.$class))
{
require_once $path;
return true;
}
}
return false;
}
The first line of the loader
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
$class = static::normalizeClass($class);
will “normalize” the classname. In this context, normalize means turning the class name into a file path by replacing any backslash namespace separators with directory path separators (“/
” on *nix systems), and adding .php
to the end.
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
public static function normalizeClass($class)
{
if ($class[0] == '\\') $class = substr($class, 1);
return str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $class).'.php';
}
This means the autoloader will normalize a class name like Namespace\Some\Class
into Namespace/Some/Class.php`.
Next, the class loader will run through a list of directories
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
foreach (static::$directories as $directory)
{
//...
}
and for each one try to load a class definition using the just normalized class name prepended with the directory name
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
if (file_exists($path = $directory.DIRECTORY_SEPARATOR.$class))
{
require_once $path;
return true;
}
You’re probably wondering how $dirctories
is populated — if you take a look at the app/start/global.php
file, you’ll see the following
#File: app/start/global.php
ClassLoader::addDirectories(array(
app_path().'/commands',
app_path().'/controllers',
app_path().'/models',
app_path().'/database/seeds',
));
If we take a look at the definition for addDirectories
#File: vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php
public static function addDirectories($directories)
{
static::$directories = array_unique(array_merge(static::$directories, (array) $directories));
}
we can see this methods adds each directory path to the static::$directories
array. This means that, by default, the Laravel autoloader will search for our example Namespace\Some\Class
class in
/path/to/laravel/app/commands/Namespace/Some/Class.php
/path/to/laravel/app/controllers/Namespace/Some/Class.php
/path/to/laravel/models/Namespace/Some/Class.php
/path/to/laravel/database/seeds/Namespace/Some/Class.php
So that’s Laravel’s first autoloader, but what about the second one?
Laravel Alias Class Loader
Next up, we’re going to cover the Illuminate\Foundation\AliasLoader::load
autoloader. This is not Laravel’s primary autoloader. In fact, we’ll learn it doesn’t load any class files at all.
Let’s look at its definition.
#File: vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php
public function load($alias)
{
if (isset($this->aliases[$alias]))
{
return class_alias($this->aliases[$alias], $alias);
}
}
This is a pretty simple method, but a method that doesn’t actually load class definitions. Instead, this autoloader looks for the class name in the aliases
object property (populated by the aliases configuration in app/config/app.php
), and if it finds one, uses the class_alias function to define the alias. This effectively creates a lazy loader for Laravel’s PHP class aliases — the alias is only defined if it’s used.
So, when a program sends a class name like Str
to this autoloader, the autoloader finds the alias defined as Illuminate\Support\Str
#File: app/config/app.php
'Str' => 'Illuminate\Support\Str',
and then defines the alias
#File: vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php
return class_alias('Illuminate\Support\Str', 'Str');
If you’re curious, Laravel populates the $this->alias
property in the AliasLoader
constructor
#File: vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php
public function __construct(array $aliases = array())
{
$this->aliases = $aliases;
}
PHP calls this constructor when Laravel instantiates the AliasLoader
object, which happens in the framework start.php
file
#File: laravel/framework/src/Illuminate/Foundation/start.php
$config = $app['config']['app'];
//...
$aliases = $config['aliases'];
AliasLoader::getInstance($aliases)->register();
PHP includes this framework start.php
file from the bootstrap start.php
file
#File: bootstrap/start.php
$framework = $app['path.base'].
'/vendor/laravel/framework/src';
//...
require $framework.'/Illuminate/Foundation/start.php';
Finally, bringing us back to the top of the stack, PHP includes the bootstrap start.php
file in the main index.php
file.
#File: index.php
$app = require_once __DIR__.'/../bootstrap/start.php';
This early stage loading of the AliasLoader
class will become important in future articles when we start to investigate how Laravel’s autoloaders interact with one another. There’s some subtle timing elements you’ll need to be aware of.
The Swift Autoloader
Laravel comes bundled with “a clean, simple API over the popular SwiftMailer library”. However, like many venerable and popular PHP projects, SwiftMailer predates the popularity of the modern PSR autoloading standards, and has its own Swift::autoload
callback for autoloading.
Step one for any Laravel programmer is figuring out if Swift
is a Laravel facade, class alias, or just a plain old global class. In other words, is Swift::autolaod
part of that clean, simple API over the popular SwiftMailer library, or is it a class that’s part of the SwiftMailer library itself. Reflection can help us out here — try running the following in a Laravel bootstrapped environment (i.e. a route function or controller action)
$class = new ReflectionClass('Swift');
var_dump($class->getFilename());
You should get something like the following
string '/path/to/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php'
If we open up this class file returned by getFilename()
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
<?php
//...
abstract class Swift
{
//...
}
we see there’s no namespace declaration. The file contains only an abstract class named Swift
. This means Swift
is not an alias (and by inference, not a Laravel facade), and instead a plain old fashion global class. While a modern PHP developer may be put off by this, given the SwiftMailer library dates back to 2004, it’s not exactly surprising.
We’ll get to how Laravel ends up including this autoloader in a later article — for now let’s take a look at its implementation.
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
class Swift
{
//...
public static function autoload($class)
{
// Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift_')) {
return;
}
$path = dirname(__FILE__).'/'.str_replace('_', '/', $class).'.php';
if (!file_exists($path)) {
return;
}
require $path;
if (self::$inits && !self::$initialized) {
self::$initialized = true;
foreach (self::$inits as $init) {
call_user_func($init);
}
}
}
//...
}
The first thing you’ll notice is this conditional
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
// Don't interfere with other autoloaders
if (0 !== strpos($class, 'Swift_')) {
return;
}
Here, the SwiftMailer autoloader examines the class name, if it doesn’t start with the string Swift_
, the autoloader method returns early. Per the comments, this means the swift autoloader will ignore other class files and play nice with other autoloaders. This assumes, of course, those classes don’t also start with Swift_
— these were the horrors of a pre-namespace world.
Next, the autoloader does a pretty standard bit of classname to path manipulation
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
$path = dirname(__FILE__).'/'.str_replace('_', '/', $class).'.php';
This turns a class name like Swift_Foo_Bar
into Swift/Foo/Bar.php
, and then prepends it with the directory path the abstract Swift
class lives in. At the end of this you’d have
"/path/to/laravel/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Foo/Bar.php"
Then, the autoloader ensures the file exists
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
if (!file_exists($path)) {
return;
}
and if so, uses require
to load the class definition
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
require $path;
Up until now, this is a pretty standard pre-PSR autoloader. However, there’s one last bit of code to contend with
#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php
if (self::$inits && !self::$initialized) {
self::$initialized = true;
foreach (self::$inits as $init) {
call_user_func($init);
}
}
Here, the SwiftMailer developers couldn’t resist a little systems programming, and have hijacked the autoloader process to do some bootstraping of their framework (remember, it’s frameworks all the way down). If there are any callbacks defined in the static $inits
array, the first time a program uses a SwiftMailer class the autoloader will call those callbacks.
Composer Autoloader
Finally, we’re going to take a brief look at the Composer autoloader, Composer\Autoload\ClassLoader::loadClass
. You’ll find its definition here
#File: vendor/composer/ClassLoader.php
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
This is a small, well abstracted function that uses the class name to find the class definition file with the findFile
method
#File: vendor/composer/ClassLoader.php
if ($file = $this->findFile($class)) {
//..
}
and then includes that file with the includeFile
function
#File: vendor/composer/ClassLoader.php
includeFile($file);
If you’re a new, or even intermediate programmer, you may be a little confused by the equality assignment in the conditional (note the single =
for assignment, not comparison).
#File: vendor/composer/ClassLoader.php
if ($file = $this->findFile($class)) {
//...
}
This line could be written more verbosely as
#File: vendor/composer/ClassLoader.php
$file = $this->findFile($class);
if($file)
{
//...
}
Some programmers like this style because it reduces the number of code lines by 1. Other programmers don’t like it because they’ve addad a hard coded test in their brain that says “assignment in a conditional is a typo-bug”. We’ll leave that argument for the ages because we still need to look at the definitions of findFile
and includeFile
.
We’ll start with the simpler of the two — includeFile
. You’ll find this function definition in the same file as the Composer classLoader
#File: vendor/composer/ClassLoader.php
function includeFile($file)
{
include $file;
}
Your knee jerk response may be to question creating a new, globally scoped function with a single line to include
a file. However, if you read this function’s comments
#File: vendor/composer/ClassLoader.php
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
you’ll see the Composer autoloader developers did this deliberately to avoid calling include
from a class method. When you call include
or require
inside a method or function, the included PHP file inherits the function or class-method scope. The Composer team wanted to make sure their autoloader introduced no additional scope into the class files, so they used a global function with no additional variables (except, of course, the $file
variable). Some may think this is a premature optimization — others would call it smart preventative system engineering. I call it something you’ll want to be aware of if you’re poking around the guts of Composer’s autoloader.
That digression out of the way, let’s take a look at the definition of the findFile
method, which does the bulk of the Composer autoloader’s work
#File: vendor/composer/ClassLoader.php
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
The first code block
#File: vendor/composer/ClassLoader.php
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
Is to handle an early PHP 5.3 bug where, in some circumstances, PHP would pass the autoloader a class with the global namespace separator prepended and in other cases would not. It’s the second behavior that’s the defined standard, so this block makes sure a class string of \All\The\Great\Classes
is normalized to All\The\Greate\Classes
Next, Composer looks for the specified class in its classMap
array — if found, the method will return the configured file path, and its job is done.
#File: vendor/composer/ClassLoader.php
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
If we’re still around, Composer calls the findFileWithExtension
function to generate a path name.
#File: vendor/composer/ClassLoader.php
$file = $this->findFileWithExtension($class, '.php');
We’re not going to cover the implementation of findFileWithExtension
right now, except to say it implements both the PSR-0, and PSR-4 autoloading standards.
The next block is an interesting bit for folks on the bleeding edge of PHP development
#File: vendor/composer/ClassLoader.php
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
If findFileWithExtension
didn’t find a .php
file, and the autoloader determines we’re running Facebook’s “Hack” PHP runtime (i.e. there’s a defined HHVM_VERSION
constant), the autoloader calls the findFileWithExtension
function again, but this time searching files with a .hh
extension.
Finally, if the class file wasn’t found, Composer makes note of it in the classMap
array
#File: vendor/composer/ClassLoader.php
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
This way, if the PHP calls the autoloader again with the same class (possible in a multi-autoloader environment), Composer’s autoloader will skip the work of looking for the file again.
Wrap Up
As we said, that’s a brief summation of Composer’s autoloader. Before we can get to how all four autoloaders interact with one another, we’ll need to jump another level down the rabbit hole and examine how Composer populates its classMap
property, and what, exactly, it’s doing in the findFileWithExtension
method. That will be our topic next time when we take a quick detour from Laravel with Complete Composer Autoloading.