PSR-4 and Autoloading: Escaping the Include Chain
Moving away from manual class includes to standard autoloading: the difference between PSR-0 and PSR-4, and Composer integration.
Last month I wrote about switching to Composer. In that post I briefly touched on the autoload section. This time I want to go into more detail, because PSR-4 and autoloading genuinely require a change in habits.
My old habit was to require_once every class I needed at the top of each file. As the project grew, so did the list. In some files I had more than ten lines of require_once chains.
<?php
require_once 'lib/Database.php';
require_once 'lib/User.php';
require_once 'lib/Auth.php';
require_once 'helpers/Validator.php';
// ... and it keeps going
There are several problems with this approach: you have to keep track of where every file lives, a typo in the path throws an error, and if you move a directory you have to update every require. On top of that, including the same class in multiple places triggers a PHP warning — require_once prevents that, but it bloats the code.
Once I renamed a class and had to manually update the require_once path in seven different files that used it. That day I realized this approach simply was not sustainable.
What is PSR?
PSR (PHP Standards Recommendation) is a set of application standards published by PHP-FIG (PHP Framework Interop Group). They establish shared conventions so that PHP libraries and frameworks can interoperate with each other.
Two standards relate to autoloading: PSR-0 and PSR-4. Both define a mapping between a class name and its file path.
The Difference Between PSR-0 and PSR-4
PSR-0 appeared in 2009 and treated both the namespace separator \ and the underscore _ as directory separators. This made it compatible with older naming systems like Vendor_Package_ClassName.
Vendor\Package\ClassName → vendor/Package/ClassName.php
Vendor_Package_ClassName → vendor/Package/ClassName.php
PSR-4 was introduced to reduce the complexity PSR-0 brought. It uses only the namespace separator \ and drops the underscore conversion. The result is cleaner and more predictable:
App\Http\Controller → src/Http/Controller.php
PSR-4 is the active standard today; PSR-0 is deprecated. Use PSR-4 in new projects.
Setting Up PSR-4 with Composer
You add an autoload section to composer.json:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
This means: look for any class starting with App\ inside the src/ directory. So the class App\Http\UserController maps to src/Http/UserController.php.
Then you regenerate the autoload map:
composer dump-autoload
And you add a single line to your project’s entry point:
<?php
require 'vendor/autoload.php';
$controller = new App\Http\UserController();
The require_once chain is gone. PHP now finds the file automatically the moment you use the class.
Directory Structure
With PSR-4, the directory structure mirrors the namespace structure:
src/
├── Http/
│ ├── UserController.php → App\Http\UserController
│ └── PostController.php → App\Http\PostController
├── Models/
│ └── User.php → App\Models\User
└── Services/
└── Mailer.php → App\Services\Mailer
This layout becomes much easier to manage as the project grows. Adding a new class means simply dropping the file in the right directory — nothing else needs to be updated.
Having the namespace structure reflect the directory structure means you can always predict where things live. When you want to find the App\Services\Mailer class, you go straight to src/Services/Mailer.php. This also helps anyone joining the project get up to speed quickly.
Multiple Namespaces
If your project has more than one namespace, you can define them all:
{
"autoload": {
"psr-4": {
"App\\": "src/",
"Tests\\": "tests/"
}
}
}
The Classmap Option
For legacy code that does not follow PSR-0/PSR-4, there is classmap. Composer scans the directory you specify and maps every class to its file:
{
"autoload": {
"classmap": ["lib/"]
}
}
This is useful for pulling old code that has no naming conventions into the autoloading system. For anything you write from scratch, prefer PSR-4.
The practical difference between classmap and PSR-4 is this: classmap rescans the directory and produces a static map every time you run composer dump-autoload. PSR-4 applies the convention at runtime, so as long as you put the file in the right place, you do not need to run dump-autoload at all. That small convenience adds up over the course of a normal development day.
Conclusion
Adopting PSR-4 and Composer autoloading may look like a small change, but it makes a real difference in day-to-day work. You never have to write a require by hand, moving directories hurts a lot less, and your project interoperates cleanly with any other PSR-compliant library. Even if you are not using a framework, embracing this standard keeps your codebase organized.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.