Categories


Archives


Recent Posts


Categories


Laravel Autoloader Interactions

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.

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

Pop quiz hotshot! You’ve put in a long day of programming. Your data models are complete, the prototype UI is done. You’re going to end the day creating a simple Laravel command. You run command:make

$ php artisan command:make PulsestormSomeCommand

You register your command

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

and then run artisan list to make sure it’s registered

$ php artisan list
{"error":{  "type":"ErrorException",
            "message":"Argument 1 passed to
            Illuminate\\Console\\Application::add() must be an 
            instance of Symfony\\Component\\Console\\Command\\Command,
            instance of PulsestormSomeCommand given, called in
            ...src\/Illuminate\/Support\/Facades\/Facade.php on line 208 
            and defined",
            //...

What do you do?

Bad Dennis Hopper impersonation aside, this is a real thing that’s happened to me twice. The first time it was tremendously confounding and I ended up wasting a few hours solving the problem. The second time I managed to remember the root cause relatively quickly, but it was still a bizarre wtf moment, and it’s what led to this Laravel/Composer autoloader series.

Today we’re going to talk about how Laravel’s autoloaders interact with each other when they’re loading the basic Laravel MVC class types. We’ll start with a definition of those MVC types, then move on to discussing how each type is loaded. Then, we’ll wrap up with a few examples of how Laravel 4.2’s autoloader introduces ambiguities that can be hard to figure out if you’re new to the platform — including an answer for the scenario above.

As a reminder, this series covers the specifics of Laravel 4.2 — but the concepts should be applicable across versions.

Laravel’s MVC Types

Types, or type systems, are another area where 50+ years of computer science and business practices have led to an overloading of terms. A “type system” might refer to the native types available in your language. For example, PHP has integers, floats, arrays, objects, strings, etc. A type system might also refer to the classes and objects you yourself have built for dealing with a particular problem domain (Vehicle, Car, Pontiac, etc). Languages like ruby, where everything is an object, can be particularly confusing on this issue, as ruby only has one type (an object), but the ruby standard library has classes which allow you to create an integer object, string object, etc.

All in all, when a developer says “type system” what they mean will be heavily influenced by the system they’re working in, and what it means to their day to day working and thinking.

When we say MVC types, we’re referring to the high level objects, classes, and methodologies Laravel provides for developers building web applications. Laravel itself has an immeasurable number of types under the hood — there’s Manager classes, Driver classes, etc. Understanding those types is important, but we’re not interested in those today. The “MVC types” we’re going to talk about are the types you’ll use most if you’re building Laravel applications.

The Types

The six Laravel types we’re interested in are

Controllers

Controllers are the C in MVC, and contain a entry point for any URL into your application. Laravel’s routing system is a little different than other MVC applications — your routes are setup with PHP callbacks in app/routes.php, and often won’t include any controllers. However, for larger applications with many entry points it makes sense to start categorizing request types with controller classes.

You can find Laravel 4.2 controllers in the app/controllers folder.

Models

Models are the M in MVC. There’s two schools of thought on models — some people think they’re the classes that interact with your database/datastore — other people think they’re the classes that contain the “business or domain logic” for your application. Laravel has a folder for model classes, and provides the Eloquent ORM for talking to the database.

You can find Laravel 4.2 models in the app/models folder

Artisan Commands

Laravel 4.2 ships with a system for creating command line programs. This system is named artisan, and is based on the popular Symfony Console component. By creating your command line scripts and programs with artisan, you don’t need to worry about code for parsing arguments, creating help files, etc., and you also improve discoverability of these commands for third parties using your program.

You can find Laravel 4.2 command classes in the app/commands folder, and you’ll (typically) register commands in the app/start/artisan.php file

Database Migrations

Laravel’s database migration system allows you to define a set of programmatic rules for modifying a database’s schema (adding tables, columns, etc.), and undoing those changes (“rolling back”). Migrations are critical for teams with multiple members, and also provide the ability to easily reinstall/rebuild the application on a different set of servers.

You can find Laravel 4.2 migration classes in the app/database/migrations folder.

Database Seeds

If Database migrations are for managing a database schema — seeds are for managing the default data inside a database. Some developers scoff at the idea of requiring your application to have a certain set of data available in the database to function, but other developers do not. A seed object contains code for “seeding” a database with default data.

You can find Laravel 4.2 database seed classes in the app/database/seeds folder.

Test Cases

While Laravel doesn’t have its own test system, A default Laravel 4.2 project does come configured to run phpunit test cases inside a Laravel bootstrapped environment.

You can find Laravel 4.2’s phpunit tests in the app/tests folder.

Autoloading Laravel’s Six MVC Types

Now that we have basic definitions for Laravel’s six MVC types, we’re going to discuss how Laravel’s autoloader chain interacts with each of these types.

First up are controllers. In Laravel 4.2 you set up a controller with something like the following in your routes.php file

Route::get('some-path/{id}', 'MySpecialController@showProfile');

and then define you controller in the following file

#File: app/controllers/MySpecialController.php
class MySpecialController extends BaseController
{
    public function showProfile($id)
    {
    }
}

Laravel will automatically instantiate the controller for you. Laravel does nothing special to include or require the controller definition file. That is, it relies on the autoloader to automatically load the controller file for you.

As to which autoloader Laravel uses to load a controller class — that’s a trickier question. Since this is a Laravel MVC type, you might assume that it’s the Laravel autoloader that handles this. This is buoyed by the fact that Laravel’s app/start/global.php file includes the controller path as one to autoload from

#File: app/start/global.php
ClassLoader::addDirectories(array(
    //...
    app_path().'/controllers',
    //...    
));    

However, Laravel’s main autoloader is last on the autoloader stack

  1. Illuminate\Foundation\AliasLoader::load
  2. Composer\Autoload\ClassLoader::loadClass
  3. Swift::autoload
  4. Illuminate\Support\ClassLoader::load

This means the Composer autoloader (listed second) gets first crack at autoloading the controller class. If you take a look at a stock composer.json in a Laravel 4.2 project, you’ll see something like this

//..
"classmap": [
    "app/commands",
    "app/controllers",
    "app/models",
    "app/database/migrations",
    "app/database/seeds",
    "app/tests/TestCase.php"
]
//..

That is, Laravel 4.2 ships using the classmap autoloader we discussed in our Composer Autoloader Features articles, and the classmap autoloader is configured to scan the app/controllers folder. If you have a Laravel project, try opening up the generated class map file at vendor/composer/autoload_classmap.php, and I bet you’ll find your controller file(s) listed there.

So, this means we can say, definitively, that Laravel uses the Composer autoloader to load controller classes, right?

Wrong.

Consider the following use case — add the following to your app/routes.php file

Route::get('testbed', 'PulsestormTestbedController@test');

and then create a controller file at

#File: app/controllers/PulsestormTestbedController.php
<?php    class PulsestormTestbedController extends BaseController
{
    public function test()
    {
        var_dump("I am loaded");
    }
}

and load your URL.

http://laravel.example.com/index.php/testbed

You should see the text I am loaded dumped to the browser.

That is, Laravel successfully loaded your controller class and instantiated an object — except that shouldn’t be possible. Since we never ran composer dumpautoloader, the Composer autoloader doesn’t know about the new controller file.

Remember though, Laravel has four autoloaders

  1. Illuminate\Foundation\AliasLoader::load
  2. Composer\Autoload\ClassLoader::loadClass
  3. Swift::autoload
  4. Illuminate\Support\ClassLoader::load

After running Illuminate\Foundation\AliasLoader::load, Composer\Autoload\ClassLoader::loadCLass, and Swift::autoload, Laravel finally runs its own, internal autoloader, Illuminate\Support\ClassLoader::load. This autoloader actually loads the controller class.

So, to sum up how and where Laravel autoloads a controller class

A stock Laravel 4.2 system will use Composer classmap autoloader to load a controller’s class name. If the classmap autoloader fails to load a class, Laravel will fallback to using its own Illuminate\Support\ClassLoader::load autoloader.

This is, in my opinion, a bit of sloppy systems engineering. It introduces ambiguity as to which method will load a controller class/object — it also creates a situation where Laravel has a defined naming convention for its controller in Illuminate\Support\ClassLoader::load, but has no naming convention for controllers loaded via the classmap autoloader, (remember, Composer’s classmap generator will run through every .php and .hh file in a directory looking for classes to add).

Laravel follows a similar pattern when loading a model class. That is, a stock Laravel composer.json file ships with the app/models folder included in the classmap section

"classmap": [
    //...
    "app/models",
    //...    ]

This means the classmap autoloader will handle every .php or .hh file in app/models. However, this same “we haven’t run dumpautoload yet” rules apply. If the classmap autoloader fails to load a Model class, Laravel will fall back to Illuminate\Support\ClassLoader::load autoloader.

While I may consider this sloppy systems engineering, I can easily see a case being made that it’s good product engineering. I’m not familiar enough with the history to say for sure, but I can imagine the Laravel core team wanting to add some sort of Composer autoloader support to be in line with current PHP standards, but not wanting to remove the traditional Laravel autoloader if existing users were still using it. Backwards compatibility may be a dirty word to some, but these sort of compromises are the heart of product engineering, and impossible to judge without the context of the original development cycle.

As we’ll see below though, this classmap vs. Laravel autoloader problem can create real problems for developers who stumble into it.

Generated Code

Next, we’re going to talk about autoloading artisan command classes, database migrations, and database seed classes.

The first thing to know — the “Composer first, then Illuminate” situation we described above applies to database seed classes, and artisan commands. Again, if we look at composer.json

"classmap": [
    "app/commands",
    //...
    "app/database/seeds",
    //...
]

We see both the app/commands folder and app/database/seeds folders are part of the Composer classmap autoloader. If we then look at the default folders the Illuminate autoloader will look through

ClassLoader::addDirectories(array(

    app_path().'/commands',
    //...
    app_path().'/database/seeds',
    //...
));

We see both the app/commands folder and app/database/seeds folder.

Confusingly though, the Composer classmap/Illuminate loader fallback does not apply to database migrations. Autoloading Laravel’s migrations is handled strictly by the classmap autoloader — you won’t find the migrations folders added to the Illuminate ClassLoader. This is most likely due to the special naming of migration files (YYYY_MM_DD_name.php), which means the Illuminate autoloader has no chance of loading these class files.

There’s another wrinkle to commands and migrations (but not seeds), and that’s how a working Laravel developer creates them. While you’ll typically create models and controllers by hand, when it comes to migrations, seeds, and commands you’ll be using code generation.

That is, when you want to add a migration, you use artisan‘s migrate:make command

$ php artisan migrate:make add_some_table_to_database
Created Migration: 2015_02_06_164452_add_some_table_to_database
Generating optimized class loader

Notice the second line of output (Generating optimized class loader). Behind the scenes artisan is running composer dumpautoloader for you, and this regenerates the classmap autoloader files. i.e., your migration class file in 2015_02_06_164452_add_some_table_to_database.php is ready to be autoloaded.

There’s also a command for generating new artisan commands

$ php artisan command:make MyCommandClassName
Command created successfully.

Curiously, despite knowing about the new class, Laravel doesn’t automatically run composer dumpautoload for you in this situation — perhaps because the core artisan developers know the Illuminate class loader will take care of this.

Test Cases

The last Laravel MVC type we’ll talk about are your test classes. Tests are a little different, because they don’t run in a pure Laravel bootstrapped environment. Instead, tests run in a phpunit bootstrap environment, and this phpunit environment bootstraps a Laravel environment.

This may seem a pedantic distinction — but it has important consequences. For example, your test classes in app/tests? Laravel does not autoload these. In fact, your test classes aren’t autoloaded at all. PHPUnit manually includes these files (for reasons both historic and practical). If you’re curious where this happens in PHPUnit’s systems code, take a look at this method

#File: vendor/phpunit/phpunit/src/Util/Fileloader.php
public static function load($filename)
{
    $oldVariableNames = array_keys(get_defined_vars());

    include_once $filename;

    $newVariables     = get_defined_vars();
    $newVariableNames = array_diff(
        array_keys($newVariables), $oldVariableNames
    );

    foreach ($newVariableNames as $variableName) {
        if ($variableName != 'oldVariableNames') {
            $GLOBALS[$variableName] = $newVariables[$variableName];
        }
    }

    return $filename;
}

So why are we mentioning test classes if there’s no autoloader? While PHPUnit autoloads individual test classes, it does not load any base test classes you may have. Laravel itself ships with a base test class named TestCase

#File: app/tests/TestCase.php     
class TestCase extends Illuminate\Foundation\Testing\TestCase {

    /**
     * Creates the application.
     *
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface
     */
    public function createApplication()
    {
        $unitTesting = true;

        $testEnvironment = 'testing';

        return require __DIR__.'/../../bootstrap/start.php';
    }

}

It’s this base test case that bootstraps the Laravel testing environment.

So, if PHPUnit doesn’t load this class, what does? Take one last gander at Laravel’s stock composer.json

"classmap": [
    //...
    "app/tests/TestCase.php"
]

Here Laravel has explicitly added this base test case to the classmap autoloader.

If you wanted to create your own base tests classes, you’d do the same. That is, you’d add the file to composer.json

"classmap": [
    //...
    "app/tests/MyBaseTestCase.php"
]

And then run the dumpautoload command

$ composer dumpautoload

Swift Autoloader

There’s one last class type to talk about, and it’s not a Laravel MVC class — it’s any class that’s a part of the Swift Mailer library.

#File: vendor/swiftmailer/swiftmailer/lib/classes/Swift.php    
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;

    //...
}

There’s a small chance this legacy autoloader might cause class name conflicts in your program. For example, try running the following code in a Laravel bootstrapped environment (i.e. a Laravel Route callback or controller action)

$object = new Swift_Message;
$r      = new ReflectionObject($object);
var_dump($r->getFilename());

This bit of code creates a Swift_Message object, and then uses PHP’s reflection class to find the class definition. In a working system, it will var_dump the following file path.

string '/path/to/laravel/login/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php' 

Now, crete the following class file

#File: app/models/Swift_Message.php
<?php    class Swift_Message
{
}

and run the Composer dumpautoload command

$ composer dumpautoload

If you re-run the reflection code above, you’ll see the following

string '/path/to/laravel/app/models/Swift_Message.php' 

That is, PHP is now loading the class we created instead of the native SwiftMailer Swift_Message class. This happens because of the autoloader stack order

  1. Illuminate\Foundation\AliasLoader::load
  2. Composer\Autoload\ClassLoader::loadClass
  3. Swift::autoload
  4. Illuminate\Support\ClassLoader::load

The Composer\Autoload\ClassLoader::loadClass autoloader wins out over Swift::autoload because PHP calls Composer\Autoload\ClassLoader::loadClass first.

Now, it’s unlikely you’ll run into this problem yourself, you’d need to be naming your classes with a Swift prefix and not following the standard naming conventions for model classes. However, it is theoretically possible, and something you’ll want to watch out for.

Framework vs. Application

Another area you’ll want to be careful with is conflicts between your application classes and framework classes.

For example, in PHP, there’s nothing to stop you from defining a class in Laravel’s Illuminate namespace.

#File: app/models/Illuminate/View/Test.php
<?php    namespace Illuminate\View;
class Test
{
}

With the above in place, give the following reflection snippet a try

$reflection = new ReflectionClass('Illuminate\View\Test');
var_dump($reflection->getFilename());

PHP will print out the path to the class, letting us know we’ve correctly defined it.

string '/path/to/laravel/app/models/Illuminate/View/Test.php' (length=88)

Next, we’re going to create a Illuminate\View\View class

#File: app/models/Illuminate/View/Test.php
<?php    namespace Illuminate\View;
class View
{
}

If we run this one through our reflection snippet

$reflection = new ReflectionClass('Illuminate\View\View');
var_dump($reflection->getFilename());    

we’ll see that PHP does not return a path to our class — instead, it returns the path to the real Illuminate\View\View class in the Laravel framework library.

string '/path/to/login/vendor/laravel/framework/src/Illuminate/View/View.php' (length=106)

So far so good — this is reasonable behavior to expect from a framework. However, without adding any extra code, run the dumpautoload command,

$ composer dumpautoload

and try running the reflection snippet again

string '/path/to/app/models/Illuminate/View/View.php' (length=88)

This time PHP returns the path to the Illuminate\View\View class we defined. That’s because the Composer classmap autoloader wins out over the PSR-0 autoloader defined in the framework composer.json

Other PHP frameworks promote this sort of behavior as a way to selectively replace core system/framework classes — if you’re a Magento developer you’re probably familiar with code pool overrides. While you may be tempted to take advantage of this behavior for a quick fix to an application you don’t understand, without an implicit promise from the framework developer to maintain this behavior, I’d treat this as a side effect to be wary of rather than a feature to rely on.

The Answer to this Week’s Puzzler

So, with all tha said, have you figured out the pop quiz question we led off with?

The answer is, in part, pilot error. What I left out was that, immediately prior to running command:make, I had absentmindedly run the following

$ php artisan migrate:make PulsestormSomeCommand
Created Migration: 2015_02_08_153827_PulsestormSomeCommand
Generating optimized class loader

Now, this was obviously a dumb thing to do — but what this did was create a PulsestormSomeCommand class in the migrations folder.

#File: app/database/migrations/2015_02_08_153827_PulsestormSomeCommand.php 
//...
class PulsestormSomeCommand extends Migration {
    //...    
}

Then, when I realized I’d accidentally run the wrong command, I ran the correct one

$ php artisan command:make PulsestormSomeCommand
Command created successfully.

And Laravel created a command class in the app/commands folder

#File: app/commands/PulsestormSomeCommand.php
//...    
class PulsestormSomeCommand extends Command {
    //...    
}

So, what I had now was a system with two classes named PulsestormSomeCommand in the global namespace. Laravel itself still served pages fine — because Laravel loads neither the command or migration classes during normal system execution.

However, when I tried to run php artisan, PHP ended up instantiating the PulsestormSomeCommand migration class. With that context, the error message

Argument 1 passed to Illuminate\Console\Application::add() must be an instance of Symfony\Component\Console\Command\Command, instance of PulsestormSomeCommand given, called in

makes a lot more sense. Laravel’s telling us it can’t use our migration class as a command class.

Transitionary Period

This brings us, at long last, to the end of Laravel’s autoloader architecture. Beyond the utility of being able to debug autoload related bugs and day-to-day blocking issues, Laravel’s autoloader offers an interesting perspective on the the tradeoffs of systems engineering vs. product engineering.

The autoloader architecture in Laravel 4.2, while solid, does show signs of being in transition. Next time we’ll tie a bow on our series with a look at the changes to this architecture in the recently released Laravel 5.

Originally published February 15, 2015
Series Navigation<< Laravel’s Framework AutoloaderLaravel 5 Autoloader >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 15th February 2015

email hidden; JavaScript is required