How Composer’s PSR-4 autoloading works
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 require
ing 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 require
ing 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.
Thanks for your comment 🙏. Once it's approved, it will appear here.
Leave a comment