Buy

Autoloading: Where did require/include go?

UPGRADE! Check out the newest version of this tutorial

Autoading: it's like plumbing. You forget it's there, but when it's gone, well, let's just say you have to go outside a bit more often.

Autoloading is the magic that lets us use classes without needing to require or include the file that holds them first. We used to have include statements everywhere, and well, it was terrible.

But an autoloader has a tricky job: given any class name, it needs to know the exact location of the file that holds that class. In many modern projects, including ours, Composer handles this, and there are two pieces to understanding how it figures out what file a class lives in.

Directory Structure and Namespaces

When we create an Event object, Composer's autoloader knows that this class lives inside src/Yoda/EventBundle/Entity/Event.php. How? It just takes the full class name, flips the slashes, and adds .php to the end of it:

Yoda\EventBundle\Entity\Event

src/Yoda/EventBundle/Entity/Event.php

As long as the namespace matches the directory and the class name matches the filename plus .php, autoloading just works. Let's mess this up - let's rename the Entity directory to Entity2:

use Yoda\EventBundle\Entity\Event;

// the src/Yoda/EventBundle/Entity/Event.php file is "included"
// .. so the file better exist and "house" the Event class!
new Event();

If we run play.php now, it fails big:

Class Yoda\EventBundle\Entity\Event not found

The autoloader is looking for an Entity directory. Rename the directory back to Entity to fix things.

Library Directory Paths

Right now, it almost looks like the autoloader assumes that everything must live in the src/ directory. So how are vendor classes - like Symfony - loaded?

That's the second part. When we fetch a library with Composer, it configures its autoloader to look for the new classes in the directory it just downloaded.

Open up the vendor/composer/autoload_namespaces.php file. This is generated by Composer and it has a map of namespaces to the directories where those classes can be found:

// vendor/composer/autoload_namespaces.php
// ...

return array(
    'Symfony\\' => array($vendorDir . '/symfony/symfony/src'),
    // ...
    'Doctrine\\ORM' => $vendorDir . '/doctrine/orm/lib/',
    'Doctrine\\DBAL' => $vendorDir . '/doctrine/dbal/lib/',
    'Doctrine\\Common\\DataFixtures' => $vendorDir . '/doctrine/data-fixtures/lib/',
    // ...
);

So when we reference a Symfony class, it does the slash-flipping trick, and then looks for the file starting in vendor/symfony/symfony/src:

Symfony\Component\HttpFoundation\Response

vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/Response.php

Now you know all the secrets about the autoloader. And when you see a class not found error, it's your fault. Sorry! The most common mistake is easily a missing use statement. If it's not that, check for a typo in your class and filename.

Leave a comment!

  • 2017-03-13 Ivan

    Wow! That's a good and very useful answer. Thanks.

  • 2017-03-13 weaverryan

    Yo Ivan!

    Cool question :). There is and is not a way to do this. This file specifically is static - you can't control what goes in there. However, there is another file called var/cache/dev/classes.php that is just like this, and this one *is* dynamic. Currently, you need to create a dependency injection extension class to add more stuff to this: http://symfony.com/doc/curr.... Then, you can add classes to it via addClassesToCompile http://symfony.com/doc/curr...

    Just make sure you don't put TOO much inside there :). It does speed things up a little bit, but it can come at a cost: if you add a class to this file, but you do *not* need that class on every request, then it is unnecessarily loaded into memory.

    Have fun!

  • 2017-03-12 Ivan

    Sorry but I have a tricky question.
    There is a file called /app/bootstrap.php.cache which contains all Symfony Components classes for the purpose of faster loading.
    But what if I want to add all /vendor/ classes to this cache? Any possible way to do that?