Black and white interlocking triangles

Why Casing Matters with PHP Autoloaders

Contrary to pop­u­lar be­lief, au­toload­ing isn’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 isn’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 didn’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 shouldn’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 doesn’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 wasn’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 doesn’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 doesn’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 isn’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.