Black and white interlocking triangles

Why Casing Matters with PHP Autoloaders

Contrary to popĀ­uĀ­lar beĀ­lief, auĀ­toloadĀ­ing isĀ­n’t magĀ­iĀ­cal. There are well de­fined rules for how your class, trait, or inĀ­terĀ­face deĀ­fĀ­iĀ­nĀ­iĀ­tions are swooped in.

My purĀ­pose here is help you unĀ­derĀ­stand the baĀ­sics beĀ­hind auĀ­toloadĀ­ing a litĀ­tle betĀ­ter. I’m asĀ­sumĀ­ing you have some prior knowlĀ­edge of PHP. It will be helpĀ­ful if you’ve used Composer as well, but I’ll try to exĀ­plain things as we go.

In my opinĀ­ion, knowĀ­ing how to write an auĀ­toloader isĀ­n’t that imĀ­porĀ­tant, so I’m not goĀ­ing to cover that in great deĀ­tail here. Rather, I want peoĀ­ple to be faĀ­milĀ­iar with the auĀ­toloadĀ­ing stanĀ­dards and some gotchas. It might seem silly at first, but I hope you gain a betĀ­ter grasp of what PHP is acĀ­tuĀ­ally doĀ­ing.

Let’s do this without the autoloader

Back in the days of yore, PHP didĀ­n’t have an auĀ­toloader. People had to get their hands dirty by manĀ­uĀ­ally inĀ­cludĀ­ing their files–or maybe they put everyĀ­thing in one big script (I see you). These aren’t the best opĀ­tions. But for next couĀ­ple of exĀ­amĀ­ples, it’s what we’re goĀ­ing to work with.

First let’s just creĀ­ate a class deĀ­fĀ­iĀ­nĀ­iĀ­tion and then use it. Scared yet?

<?php
    
namespace Tutorial;
    
class WordPrinter
{
    public function print(): void
    {
        echo 'Hello World!';
    }
}

$printer = new \Tutorial\WordPrinter();
$printer->print();

Some unĀ­necĀ­esĀ­sary boilĀ­erĀ­plate later, we have manĀ­aged to creĀ­ate a ā€œHello Worldā€ exĀ­amĀ­ple. I want to foĀ­cus on the class deĀ­fĀ­iĀ­nĀ­iĀ­tion part. The class name is \Tutorial\WordPrinter. Pay atĀ­tenĀ­tion to the casĀ­ing. Notice how the class refĀ­erĀ­ence (the part where we newed up the inĀ­stance) is cased exĀ­actly the same. Does that matĀ­ter? What hapĀ­pens if you add this to the end of file?

$printer2 = new \TuToRiAl\WoRdPrInTeR();
$printer2->print();

It prints ā€œHello World!ā€ twice. And this reĀ­sult shouldĀ­n’t be surĀ­prisĀ­ing at all. Classnames in PHP are case-inĀ­senĀ­siĀ­tive. There is nothĀ­ing crazy goĀ­ing on here yet.

Here’s anĀ­other curve ball. Add this code to the end of the file.

(function() {
    $printer = new \Tutorial\WordPrinter();
    $printer->print();
})();

We get three ā€œHello World!ā€ texts. Cool but what’s the point here. It turns out class, trait, and inĀ­terĀ­face deĀ­fĀ­iĀ­nĀ­iĀ­tions are alĀ­ways global. Say it with me kids! We can acĀ­cess the class deĀ­fĀ­iĀ­nĀ­iĀ­tion inĀ­side the funcĀ­tion, even though it has a difĀ­ferĀ­ent scope. We get a new $printer variĀ­able, but the same old \Tutorial\WordPrinter class.

This conĀ­cept is obĀ­viĀ­ous if we inĀ­verse the sitĀ­uĀ­aĀ­tion.

<?php

namespace Tutorial;

(function() {  
    class WordPrinter
    {
        public function print(): void
        {
            echo 'Hello World!';
        }
    }
})();

$printer = new \Tutorial\WordPrinter();
$printer->print();

We get a ā€œHello World!.ā€ The class deĀ­fĀ­iĀ­nĀ­iĀ­tion is not reĀ­stricted to the funcĀ­tion’s scope. Once again, this also apĀ­plies to trait and inĀ­terĀ­face deĀ­fĀ­iĀ­nĀ­iĀ­tions.

PHP keeps taĀ­bles mapĀ­ping class, inĀ­terĀ­face, and trait names to their deĀ­fĀ­iĀ­nĀ­iĀ­tions. Each reĀ­quest gets its own set of taĀ­bles. A name can only be mapped to sinĀ­gle deĀ­fĀ­iĀ­nĀ­iĀ­tion—like a dicĀ­tioĀ­nary data strucĀ­ture. This is why you can’t have a sinĀ­gle name mapped to mulĀ­tiĀ­ple deĀ­fĀ­iĀ­nĀ­iĀ­tions. PHP will throw an erĀ­ror.

<?php
// This will definitely error. The name Foo can only map to one definition.
class Foo {}
class Foo {}

Includes and reĀ­quires don’t change our sitĀ­uĀ­aĀ­tion much. Autoloadable (I’m alĀ­lowed to make up words) deĀ­fĀ­iĀ­nĀ­iĀ­tions are global once they are exĀ­eĀ­cuted. So we can put our class, traits, and inĀ­terĀ­faces into sepĀ­aĀ­rate files to keep everyĀ­thing orĀ­gaĀ­nized. Then, tell PHP exĀ­plicĀ­itly to bring the file in. Here’s a quick exĀ­amĀ­ple:

<?php

class Foo {}
<?php

require __DIR__ . '/Foo.php';

class Bar extends Foo
{
    public static function HelloThere()
    {
        echo 'Hello There';
    }
}

Bar::HelloThere();

Wouldn’t it be cool if PHP knew to drag that file in on its own?

Ok, Autoloaders

Surprise! Autoloaders inĀ­clude or reĀ­quire in the file auĀ­toĀ­matĀ­iĀ­cally. You don’t have to litĀ­ter your code with inĀ­cludes, and only the classes you need for that reĀ­quest have to be fetched. This is betĀ­ter by far.

Since PHP verĀ­sion 5.1.0, you can regĀ­isĀ­ter an auĀ­toloader usĀ­ing the spl_auĀ­toloadĀ­_regĀ­isĀ­ter funcĀ­tion. More than one can regĀ­isĀ­tered—there’s a queue of them. I only menĀ­tion this beĀ­cause you may need more than one, and their orĀ­der may matĀ­ter. Here, I’ll be usĀ­ing Composer.

Composer is the packĀ­age manĀ­ager for PHP. It also will setup an auĀ­toloader for us. Very cool. You can downĀ­load it here.

So when is the auĀ­toloader inĀ­voked? It’s used nearly everyĀ­where you need acĀ­cess to a class, inĀ­terĀ­face, or trait. Generally, you don’t have think about it. You code should ā€œjust work.ā€ PHP is smart enough to use the auĀ­toloader any time it needs to try a find an auĀ­toloadĀ­able deĀ­fĀ­iĀ­nĀ­iĀ­tion. Now, keep this in mind. PHP will not have to check the auĀ­toloader if that deĀ­fĀ­iĀ­nĀ­iĀ­tion has alĀ­ready been loaded. It augĀ­ments the beĀ­havĀ­ior we saw earĀ­lier. Let’s look at a quick exĀ­amĀ­ple:

<?php
// Assume this class is autoloadable
$foo = new Foo();
// The first time the class is referenced, the autoloader is used.
// The class Foo is now in PHP's class table
var_dump(class_exists('Foo', false));
// The class_exists function by default will use the autoloader to check if the class exists
// By passing false as the second parameter, it won't use the autoloader
// We still get true here! It's in the class table remember
var_dump(class_exists('FOO', false));
// True again! Remember PHP doesn't care about the casing for class names
// But what happens if the autoloader has to find the class FOO
// We'll look at that case in a minute

Once the au­toloader is trig­gered, your reg­is­tered func­tion (or func­tions) have to find the file that con­tains the de­f­i­n­i­tion. It would be help­ful if there was some con­sis­tent way to map de­f­i­n­i­tion names to file­names. Oh wait, there is!

PSR-0

A com­mit­tee called PHP-FIG main­tains a list of PHP stan­dards rec­om­men­da­tions (PSRs). Since pro­gram­mers like count­ing from zero, the first rec­om­men­da­tion is PSR-0, and it in­volves how to name classes to play nice with the au­toloader. You can read the stan­dard it­self for all the glo­ri­ous de­tails, but the idea is sim­ple. We are go­ing to map name­space sep­a­ra­tors to the di­rec­tory sep­a­ra­tor for the cur­rent op­er­at­ing sys­tem. Composer sup­ports this out of the box.

{
    "autoload": {
        "psr-0": {"Tutorial\\": "src/"}
    }
}

Run a php composer.phar dumpautoload for Composer to genĀ­erĀ­ate the auĀ­toloader.

Let’s get our WordPrinter class to play nice with the auĀ­toloader. For it to recĀ­ogĀ­nize our class, we’ll have to name it to match the the pre­fix proĀ­vided in the con­fig. And I’m goĀ­ing to emĀ­phaĀ­size: you have to match the casĀ­ing of the pre­fix. Composer is case-senĀ­siĀ­tive. We alĀ­ready have a Tutorial nameĀ­space deĀ­clared, so we’re good.

Where should this file be placed? Before you could put files wherĀ­ever—as long as the inĀ­clude paths were corĀ­rect. Now the nameĀ­space maps to a path. The path is alĀ­ways relĀ­aĀ­tive to the loĀ­caĀ­tion of the comĀ­poser.json. You’ll match the nameĀ­space to enĀ­try in the comĀ­poser.json and take the corĀ­reĀ­spondĀ­ing path part. Then flip all the nameĀ­space sepĀ­aĀ­raĀ­tors to diĀ­recĀ­tory sepĀ­aĀ­raĀ­tors and slap a .php to the end. So you’ll put the file here.

/path/to/composerJson/src/Tutorial/WordPrinter.php

The Tutorial name­space maps to the Tutorial folder. The cas­ing of the folder should match the cas­ing of the name­space. You have to do this on a case-sen­si­tive filesys­tem. When straight up us­ing the PSR rules, Composer has to look up things in the filesys­tem at run­time.

Eww. I know. But there’s a fix: classmaps. We’ll look at these shortly.

PSR-4

As time went on, peoĀ­ple reĀ­alĀ­ized that forcĀ­ing the nameĀ­spaces to diĀ­rectly map to the path was inĀ­conĀ­veĀ­nient. It doesĀ­n’t play as nice with how Composer hanĀ­dles packĀ­ages. You can read more about this here. Anyway, a new stanĀ­dard was proĀ­posed for auĀ­toloadĀ­ing: PSR-4. It’s not too much difĀ­ferĀ­ent on the surĀ­face of things. We can have the exĀ­act same diĀ­recĀ­tory strucĀ­ture as beĀ­fore.

{
    "autoload": {
        "psr-4": {"Tutorial\\": "src/Tutorial/"}
    }
}

You should noĀ­tice that we had to diĀ­rectly map the Tutorial nameĀ­space to the Tutorial folder in the comĀ­poser.json. But we choose to do this! It wasĀ­n’t forced on us. We could map the nameĀ­space to any folder.

{
    "autoload": {
        "psr-4": {"Tutorial\\": "src/AnotherFolder/"}    
    }
}

And that’s pretty much it. It’s kinda PSR-0, exĀ­cept the pre­fix doesĀ­n’t have to match the folder. You can imagĀ­ine we reĀ­moved the pre­fix from the class name, apĀ­pended that pre­fix’s path to our exĀ­istĀ­ing base path, and did PSR-0 the rest of the way.

Let’s creĀ­ate an exĀ­amĀ­ple usĀ­ing the above comĀ­poser.json. All the classes in the Tutorial nameĀ­space have to be in the AnotherFolder diĀ­recĀ­tory (or one its subĀ­diĀ­recĀ­toĀ­ries). The part of the nameĀ­space that doesĀ­n’t match the pre­fix in the comĀ­poser.json has to map to the filesysĀ­tem.

Let’s walk through this process.

The Tutorial part of the class name matches our pre­fix, so we’re goĀ­ing to ā€œremoveā€ it for now.

That leaves us with \Factory\SomethingFactory.php.

Extend our com­poser.json base path with the path cor­re­spond­ing to the pre­fix, like so:

/path/to/composerJson/src/AnotherFolder

And then PSR-0 the rest of the class name, adding it to the end of base path.

/path/to/composerJson/src/AnotherFolder/Factory/SomethingFactory.php

Ta-da! We mapped the class name to a path. Similar to PSR-0, Composer can look up the loĀ­caĀ­tions of files at runĀ­time usĀ­ing the PSR-4 rules. Yet, that’s slow. Let’s fix that with classmaps.

Classmap

Composer ofĀ­fers a third way to auĀ­toload—and this way is techĀ­niĀ­cally the simĀ­plest. We can genĀ­erĀ­ate a classmap. It’s simĀ­ply a key value mapĀ­ping. The key is the auĀ­toloadĀ­able item, like a class, inĀ­terĀ­face, or trait. The value is the file path. Composer will put the mapĀ­ping in the its genĀ­erĀ­ated venĀ­dor/​comĀ­poser folder. Look for the auĀ­toloadĀ­_Ā­classmap.php file. It will look someĀ­thing like this:

<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'Tutorial\\Factory\\SampleFactory' => $baseDir . '/src/AnotherFolder/Factory/SampleFactory.php',
    'Tutorial\\WordPrinter' => $baseDir . '/src/AnotherFolder/WordPrinter.php',
);

You can conĀ­vert your PSR-0 and PSR-4 rules in Composer to classmap rules by passĀ­ing the –optimized flag to the inĀ­stall or dumpauĀ­toload comĀ­mand. So someĀ­thing like php composer.phar dumpautoload --optimized.

You can also list diĀ­recĀ­toĀ­ries in the classmap secĀ­tion of the comĀ­poser.json, but this isĀ­n’t recĀ­omĀ­mended. It will force you to dumpauĀ­toload anyĀ­time you add a new file—ComĀ­poser won’t use the PSR rules. If you use the PSR rules, Composer can look up where the file is on the fly as alĀ­ready menĀ­tioned. This beĀ­havĀ­ior is conĀ­veĀ­nient in deĀ­velĀ­opĀ­ment enĀ­viĀ­ronĀ­ments—but it’s too slow for proĀ­ducĀ­tion. You should alĀ­ways creĀ­ate the opĀ­tiĀ­mized classmap for proĀ­ducĀ­tion enĀ­viĀ­ronĀ­ments. It is worth adding some time to your build.

When Composer’s auĀ­toloader is trigĀ­gered, it will try to look up the given name in the classmap first (or alĀ­ways if an auĀ­thoĀ­rĀ­aĀ­tive classmap is used). It has to exĀ­actly match. Casing matĀ­ters here! What does this mean? The casĀ­ing of your class refĀ­erĀ­ences have to match the casĀ­ing of your deĀ­fĀ­iĀ­nĀ­iĀ­tions for auĀ­toloadĀ­ing to conĀ­sisĀ­tently work.

Technically, the casĀ­ing only matĀ­ters on first refĀ­erĀ­ence. As alĀ­ready menĀ­tioned, once the deĀ­fĀ­iĀ­nĀ­iĀ­tion is loaded the name is added to one of PHP’s taĀ­bles. If it’s in the table, the auĀ­toloader is never trigĀ­gered. It can simĀ­ply use the deĀ­fĀ­iĀ­nĀ­iĀ­tion, and the key lookup there is case-inĀ­senĀ­siĀ­tive. We alĀ­ready showed that in a preĀ­viĀ­ous exĀ­amĀ­ple. Yet, Composer’s auĀ­toloader is case-senĀ­siĀ­tive (in slightly difĀ­ferĀ­ent ways deĀ­pendĀ­ing on if PSR-0, PSR-4, or the classmap is used). For everyĀ­thing to ā€œjust alĀ­ways work,ā€ you should match the casĀ­ing of your refĀ­erĀ­ences to the casĀ­ing of the deĀ­fĀ­iĀ­nĀ­iĀ­tions. And the casĀ­ing of the deĀ­fĀ­iĀ­nĀ­iĀ­tion name should match up to the file path casĀ­ings. There’s lots of casĀ­ing to pay atĀ­tenĀ­tion to here! Luckily, PHP should crash and burn if you get it wrong, so at least it should be catchĀ­able with careĀ­ful testĀ­ing.