sajad torkamani

Let’s take a closer look at how Composer’s autoloading mechanism works and how it can help us organise our code. There’s a GitHub repo that contains this post’s code snippets.

The problem: manually loading classes

Suppose we have the following directory structure:

├── main.php
├── src
│   └── Utils
│       └── Logger.php

Suppose Logger.php is a simple class and to keep things organised, we want to define it in an App\Utils namespace.

<?php

namespace App\Utils;

class Logger
{
    public function info(string $msg): void
    {
        echo "INFO: $msg";
    }

    public function error(string $msg): void
    {
        echo "ERROR: $msg";
    }
}

In our main.php file, we want to use the Logger class without manually requireing the Logger.php file. It would be nice if we could do something like this:

<?php

use App\Utils\Logger;

$logger = new Logger();
$logger->info('hello');

But if we run main.php as defined above, we’ll get an error:

PHP Fatal error:  Uncaught Error: Class "App\Utils\Logger" not found in ...

This makes sense because we haven’t required the src/Utils/Logger.php file, nor have we defined any autoloader using spl_autoload_register that can resolve the class. As a result, PHP can’t find the class definition for App\Utils\Logger and complains.

The solution: autoloading classes with PSR-4

Fortunately, Composer gives us a convenient way of autoloading classes so that with a little bit of configuration, we can tell PHP how to find the App\Utils\Logger class without requireing it.

PHP allows us to register custom autoloaders using the spl_autoload_register function. Over the years, the PHP community has developed a neat approach to autoloading called the PSR-4: Autoloader standard. This approach lets us map namespaces to directories so that PHP can find a class’s source file based on the namespace. That might sound more complicated than it is in practice so let’s walk through an example.

How to register PSR-4 autoloaders with composer

We can tell Composer to register PSR-4 autoloaders using its composer.json file. Generate this file by running the following command:

composer init -n --name "give-me-a-name-please"

We register a PSR-4 autoloader using the autoload property:

{
  "name": "sajadtorkamani/understanding-psr4-autoloading",
  "autoload": {
    "psr-4": {
      "App\\": "src"
    }
  },
  "require": {}
}

That’s all we need to configure autoloading for our present needs. Now, classes with the App namespace prefix will be looked for in the src directory (relative to the project root). This means our PSR-4 autoloader will attempt to resolve a class like App/Utils/Logger by looking for the file src/Utils/Logger.php. Similarly, it’d look for the class App/Models/User in the file src/Models/User.php.

We can define multiple mappings in our composer.json file. For example, we could add another mapping from the namespace Acme\\ to the path lib/Acme, This will mean that a class like Acme/Services/UserService will be looked for in lib/Acme/Services/UserService.php .

Run composer dump-autoload from your terminal to generate a vendor/autoload.php file. You need to run this command every time you update the autoload property.

Now, you can require the vendor/autoload.php at the start of main.php and the script should run without any errors.

<?php

require_once 'vendor/autoload.php'; # Add this line

use App\Utils\Logger;

$logger = new Logger();
$logger->info('hello');

The vendor/autoload.php file will take care of registering the correct autoloaders. If you’re feeling curious, you can inspect this file and find the calls to spl_autoload_register where Composer uses the autoload configuration from composer.json to register your autoloader.

Wrapping up

I remember being very confused when I was trying to wrap my head around how the autoloading stuff worked. Hopefully, this article has helped clarify things for you. I recommend creating a fresh PHP project and experimenting with different autoload mappings.

Tagged: PHP