Categories


Archives


Recent Posts


Categories


Laravel 5 Autoloader

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Last time we finished up our look at Laravel 4.2’s autoloader implementation. Like a lot of features in Laravel, (or any framework), once you pull out the microscope sharp edges begin to jut out everywhere.

However, unlike many other framework teams, the Laravel core team is willing to make shifts in their platform and application architecture. If you’re familiar with the internals of Laravel 4, looking at the internals of Laravel 3 may be a little disorienting. Similarly, the recent release of Laravel 5 presents some new wrinkles at the system level.

Today we’re going to examine changes to the autoloader in Laravel 5, and how they address some of the issues and edge cases we encountered when exploring Laravel 4.2’s autoloader.

Laravel 5 Registered Autoloaders

The first thing we’ll want to do is examine which autoloaders Laravel registers in a typical Laravel 5 HTTP request. One way to do this is to add the following route definition (note the new app/Http/routes.php location in Laravel 5)

#File: app/Http/routes.php
Route::get('/show-autoloaders', function(){
    foreach(spl_autoload_functions() as $callback)
    {
        if(is_string($callback))
        {
            echo '- ',$callback,"\n<br>\n";
        }

        else if(is_array($callback))
        {
            if(is_object($callback[0]))
            {
                echo '- ',get_class($callback[0]);
            }
            elseif(is_string($callback[0]))
            {
                echo '- ',$callback[0];
            }
            echo '::',$callback[1],"\n<br>\n";            
        }
        else
        {
            var_dump($callback);
        }
    }
});

This code loops over each registered autoloader, and attempts to echo back something sane, (based on the sort of callback registered). With the above code in place, if you load the following URL in your application

http://laravel5.example.com/index.php/show-autoloaders

You’ll see the following output

- Illuminate\Foundation\AliasLoader::load 
- Composer\Autoload\ClassLoader::loadClass 
- PhpParser\Autoloader::autoload 
- Swift::autoload 

So, right off the bat we see some familiar friends. The AliasLoader::load method is still lazy loading Laravel’s aliases. The Composer ClassLoader::loadClass method is still taking care of Composer’s four autoloading methodologies, and the Swift::autoload method is still there, taking care of its legacy duties.

The first change that pops out is the addition of the PhpParser\Autoloader::autoload method. However, more significant than that — Laravel’s native Illuminate\Support\ClassLoader::load is gone! This is, by far, the most significant change to the autoloader architecture in Laravel 5. Instead of the dueling Composer and Illuminate autoloaders, Laravel’s thrown all its autoloading eggs in the Composer basket. We’ll talk more about the impact of this below, but first let’s get a quick explanation of the PhpParser\Autoloader::autoload method out of the way.

PhpParser Autoloader

The PhpParser project is a third party library for creating an abstract syntax tree for PHP files, and is implemented in PHP itself. If that didn’t make sense, it’s a library that makes writing code analysis tools simpler. Laravel itself doesn’t use the PhpParser library for anything, but libraries that Laravel does use have PhpParser listed as a dependency, and this autoloader comes along for the ride.

If we look at the autoload section in the PhpParser composer.json file

#File: vendor/nikic/php-parser/composer.json
"autoload": {
    "files": ["lib/bootstrap.php"]
},

we see that PhpParser uses the files autoloader type to include the following file

#File: vendor/nikic/php-parser/lib/bootstrap.php
require __DIR__ . '/PhpParser/Autoloader.php';
PhpParser\Autoloader::register();     

As you can see, unlike most of the other files autoloaders we’ve seen, PhpParser actually uses the files autoloader for its intended purpose — registering an autoloader. If we take a look at the PhpParse\Autoloader::autoload method itself

#File: vendor/nikic/php-parser/lib/PhpParser/Autoloader.php    
static public function autoload($class) {
    if (0 === strpos($class, 'PhpParser\\')) {
        $fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
        if (file_exists($fileName)) {
            require $fileName;
        }
    } else if (0 === strpos($class, 'PHPParser_')) {
        if (isset(self::$oldToNewMap[$class])) {
            self::registerLegacyAliases();
        }
    }
}

we see a standard autoloader and a bit of legacy code handling.

The first conditional checks if the requested class is prefixed with PhpParser\\, and if so, converts the namespace class name into a file path, and then requires the file. This is the standard autoloader.

The second block of the conditional checks if the class is prefixed with PHPParser_ — i.e., if it’s a class in the global namespace. If so, and the class name exists as a key in self::$oldToNewMap, the autoloader calls the registerLegacyAliases method, which looks like this

#File: vendor/nikic/php-parser/lib/PhpParser/Autoloader.php 
private static function registerLegacyAliases() {
    foreach (self::$oldToNewMap as $old => $new) {
        class_alias($new, $old);
    }
}

This is a clever bit of refactoring help that’s probably best explained with an example. Let’s say there’s some old code out there that looks like this

$builder = new PHPParser_Builder;

When the PhpParser team wanted to modernize and use classes with namespaces, they could have easily changed

class PHPParser_Builder
{
}

to

namespace PHPParser;
class Builder
{
}

and left it at that.

However, this means anyone using old code like $builder = new PHPParser_Builder would suddenly have a breaking application. That’s where registerLegacyAlias comes into play

#File: vendor/nikic/php-parser/lib/PhpParser/Autoloader.php        
private static function registerLegacyAliases() {
    foreach (self::$oldToNewMap as $old => $new) {
        class_alias($new, $old);
    }
}

This method runs through every entry in the self::$oldToNewMap array

#File: vendor/nikic/php-parser/lib/PhpParser/Autoloader.php        
private static $oldToNewMap = array(
    'PHPParser_Builder' => 'PhpParser\Builder',
    'PHPParser_BuilderAbstract' => 'PhpParser\BuilderAbstract',
    'PHPParser_BuilderFactory' => 'PhpParser\BuilderFactory',
//...               

and registers an alias for the old-style class

#File: vendor/nikic/php-parser/lib/PhpParser/Autoloader.php        
class_alias('PhpParser\Builder','PHPParser_Builder');      

In plain english? If a user uses the old-style PHPParser_ classes, the PhpParser autoloader will automatically alias that old-style class to a new-style class, and the legacy code will continue to work without any changes. (PHP will automatically attempt to autoload the first argument you pass to class_alias).

Nothing too out of the ordinary here — just another stand-alone pre-PSR autoloader doing its thing.

Composer Only Autoloading

The bigger change in Laravel is the move to Composer only autoloading. To get our heads around this, we’ll want to look at the stock Laravel application composer.json autoloading sections

#File: composer.json
"autoload": {
    "classmap": [
        "database"
    ],
    "psr-4": {
        "App\\": "app/"
    }
},
"autoload-dev": {
    "classmap": [
        "tests/TestCase.php"
    ]
},

as well as the Laravel framework composer.json

#File: vendor/laravel/framework/composer.json
"autoload": {
    "classmap": [
        "src/Illuminate/Queue/IlluminateQueueClosure.php"
    ],
    "files": [
        "src/Illuminate/Foundation/helpers.php",
        "src/Illuminate/Support/helpers.php"
    ],
    "psr-4": {
        "Illuminate\\": "src/Illuminate/"
    }
},

The first, and biggest change? Most classes in Laravel are now loaded with Composer’s psr-4 autoloader. For the framework code, this means Composer loads Illuminate\\ prefixed classes

#File: vendor/laravel/framework/composer.json
"psr-4": {
    "Illuminate\\": "src/Illuminate/"
}

from the following folder

vendor/laravel/framework/src/Illuminate/

Previously, the Laravel framework code used a psr-0 autoloader to do the same thing.

More radical is the introduction of the App namespace for application classes. Thanks to this bit of configuration

#File: composer.json
"psr-4": {
    "App\\": "app/"
}   

Composer will look for any class prefixed with App\\ in the app/ folder off the root of the project. This is combined with a major overhaul of how a Laravel 5 application works. When you create a Laravel 5 application

$ laravel new my-project

Laravel will automatically create a number of App\\ prefixed classes that make up your application. These changes could be an article in and of themselves, but here’s one example that demonstrates The New One True Way™.

In Laravel 4.2, if you wanted to register a new artisan command class, you’d edit a simple PHP include file in your app folder

#File: app/start/artisan.php
Artisan::add(new YourCommandClass);

In Laravel 5, you have an App\Commands\Kernel class that manages your application configuration, and you register your commands by adding to the commands array

#File: app/Http/Kernel.php    
class Kernel extends ConsoleKernel {

    //...
    protected $commands = [
        'App\Console\Commands\Inspire',
    ];    
}

You’ll also notice Laravel now generates commands in the App\Console\Commands namespace. In Laravel 4.2 the App\Console\Commands\Inspire class would have been a global class named AppConsoleCommandsInspire, or something similar.

By dumping both the Illuminate and Composer classmap autoloaders, Laravel 5 solves many of the autoloader interaction problems we discussed last time.

Other Application Autoloading Changes

Let’s take another look at Laravel 5’s stock application autoloader sections in composer.json

#File: composer.json
"autoload": {
    "classmap": [
        "database"
    ],
    "psr-4": {
        "App\\": "app/"
    }
},
"autoload-dev": {
    "classmap": [
        "tests/TestCase.php"
    ]
},

There’s two other things to make note of. First, the database/ folder (now off the root project folder, and not in the app/ folder) is still parsed by Composer’s classmap autoloader, and migrations/seeds classes still live in the global PHP namespace. This means the potential for autoloading conflicts still exists, although with only the database folder under classmap autoloading the potential surface area for conflicts is greatly reduced.

The other thing to note is Laravel’s base TestCase class is still loaded via a classmap autoloader, but has been moved to the autoloader-dev branch

#File: composer.json
"autoload-dev": {
    "classmap": [
        "tests/TestCase.php"
    ]
},

Without getting too deeply into Composer’s “dev” and “no-dev” concepts, this move into autoload-dev ensures the TestCase class will not be included when you or Composer calls dumpautoload with the the --no-dev option

composer dumpautoload --no-dev

This should only come up when you’re planning your application deployment. The short version is Composer’s “no-dev” mode ensure packages are only downloaded dependencies they need to run, and will not download the additional packages a developer would need to add new code and features to the package.

Laravel Framework Autoloader Changes

Let’s take another look at the new framework composer.json autoload section

#File: vendor/laravel/framework/composer.json
"autoload": {
    "classmap": [
        "src/Illuminate/Queue/IlluminateQueueClosure.php"
    ],
    "files": [
        "src/Illuminate/Foundation/helpers.php",
        "src/Illuminate/Support/helpers.php"
    ],
    "psr-4": {
        "Illuminate\\": "src/Illuminate/"
    }
},

In addition to the psr-4 section, there’s still a classmap and files section. The classmap autoloader hasn’t changed since 4.2 — it’s still that weird global IlluminateQueueClosure class defined in a non-PSR compliant path at src/Illuminate/Queue/IlluminateQueueClosure.php.

The files autoloader is slightly different from Laravel 4.2, in that there’s now two files included. However, just as in the previous version, neither of these files defines any class autoloaders — instead, these files define a number of top level helper functions. Splitting the helpers into two files looks like an attempt to keep Laravel specific helper functions separated from generic helper function a programmer could use separate from Laravel.

Wrap Up

To an outside observer, the changes in Laravel’s autoloader may seem arbitrary and subject to the capricious whims of open source developers. However, after examining the autoloader implementation in detail, and understanding its shortcomings in Laravel 4, we can quickly understand why Laravel’s core developers made the changes.

This pattern applies to other changes in Laravel 5, as well as changes to any programming framework, language, or system. Knowing how to use your tools is always important, but understanding how your tools are built and how they fall short is one of the best ways to cope with an ever changing technology landscape.

Originally published February 20, 2015
Series Navigation<< Laravel Autoloader Interactions