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Ā­ļ¬ned 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 ļ¬rst, 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 ļ¬lesā€“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 ļ¬le?

$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 ļ¬le.

(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 ļ¬les to keep everyĀ­thing orĀ­gaĀ­nized. Then, tell PHP exĀ­plicĀ­itly to bring the ļ¬le 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 ļ¬le in on its own?

Ok, Autoloaders

Surprise! Autoloaders inĀ­clude or reĀ­quire in the ļ¬le 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 ļ¬nd 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 ļ¬nd the ļ¬le 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 ļ¬leĀ­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 ļ¬rst 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Ā­ļ¬x proĀ­vided in the conĀ­ļ¬g. And Iā€™m goĀ­ing to emĀ­phaĀ­size: you have to match the casĀ­ing of the preĀ­ļ¬x. Composer is case-senĀ­siĀ­tive. We alĀ­ready have a Tutorial nameĀ­space deĀ­clared, so weā€™re good.

Where should this ļ¬le be placed? Before you could put ļ¬les 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 ļ¬‚ip 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 ļ¬le 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 ļ¬lesysĀ­tem. When straight up usĀ­ing the PSR rules, Composer has to look up things in the ļ¬lesysĀ­tem at runĀ­time.

Eww. I know. But thereā€™s a ļ¬x: 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Ā­ļ¬x doesĀ­nā€™t have to match the folder. You can imagĀ­ine we reĀ­moved the preĀ­ļ¬x from the class name, apĀ­pended that preĀ­ļ¬xā€™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Ā­ļ¬x in the comĀ­poser.json has to map to the ļ¬lesysĀ­tem.

Letā€™s walk through this process.

The Tutorial part of the class name matches our preĀ­ļ¬x, 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Ā­ļ¬x, 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 ļ¬les at runĀ­time usĀ­ing the PSR-4 rules. Yet, thatā€™s slow. Letā€™s ļ¬x 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 ļ¬le 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 ļ¬le. 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 ļ¬‚ag 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 ļ¬leā€”ComĀ­poser wonā€™t use the PSR rules. If you use the PSR rules, Composer can look up where the ļ¬le is on the ļ¬‚y 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 ļ¬rst (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 ļ¬rst 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 ļ¬le 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.