Composer Autoloading Explained: PSR-4 vs Classmap
By Pawan Singh · Jun 2026
What Autoloading Actually Solves
Before autoloading, every PHP file needed a manual require for every class it used — painful to maintain as a codebase grows, and easy to get wrong (missing a require, or requiring the same file twice and triggering a fatal redeclaration error). Composer's autoloader instead registers a function that PHP calls automatically the first time an unknown class is referenced, which then figures out the right file to include on demand.
PSR-4: Namespace Maps to Directory
PSR-4 is the modern standard: a namespace prefix maps to a base directory, and the rest of the namespace mirrors the folder structure. Configure it in composer.json:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
With this mapping, the class App\Services\Mailer must live at app/Services/Mailer.php. No registration is needed per class — the rule is purely structural, which is why PSR-4 scales effortlessly to large codebases and needs zero regeneration when you add a new class in a properly mapped directory.
Classmap: Scan and Index Every Class
Classmap autoloading works differently — instead of a naming convention, Composer scans the specified directories/files at composer dump-autoload time and builds a direct map of every class name to its exact file path:
{
"autoload": {
"classmap": ["database/seeders", "legacy/"]
}
}
This is the right tool for legacy code that doesn't follow PSR-4 naming conventions, or files with multiple classes per file (which PSR-4 doesn't support cleanly, since PSR-4 assumes one class per file at a predictable path). The tradeoff: the map has to be regenerated every time you add a class, since there's no structural rule Composer can rely on at runtime the way it can with PSR-4.
The "files" Autoload Type: Plain Function Libraries
PSR-4 and classmap both exist to resolve classes on demand. The files autoload type is different — it's for code with no classes at all, like a global helpers file of plain functions:
{
"autoload": {
"files": ["src/helpers.php"]
}
}
Every file listed under files is required unconditionally on every single request, as soon as the autoloader boots — there's no "on demand" behavior like PSR-4/classmap, since there's no class name to trigger lazy loading against. Keep this list small; each entry adds a small fixed cost to every request regardless of whether the page actually calls any of its functions.
dev vs Production Autoloading
composer.json also supports an autoload-dev block, for classes only needed during testing (test cases, fixtures, factories):
{
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
}
}
Running composer install --no-dev in production excludes both the dev dependencies themselves and this autoload mapping — test-only classes never ship, and never need to be resolvable, on a production server.
Optimizing the Autoloader for Production
By default, PSR-4 autoloading does a small amount of runtime work per class lookup (constructing the expected path from the namespace). Two flags trade that flexibility for raw speed:
composer dump-autoload -o
composer install --optimize-autoloader --no-dev
-o (or --optimize-autoloader) converts PSR-4 mappings into a classmap for anything it can resolve statically, avoiding the runtime path computation. --classmap-authoritative goes a step further — it tells the autoloader "if a class isn't in this generated map, don't even bother checking the filesystem, just fail" — only safe once you're confident every class really is covered by the generated map, since it removes the fallback that would otherwise catch a class added but not yet mapped.
Why "Class Not Found" Happens After Adding a New Class
Composer's autoload files (vendor/autoload.php and friends) are generated, not dynamic — they're a cached snapshot. Adding a new PSR-4-compliant class doesn't strictly require regeneration (PSR-4 resolves by convention at runtime, in its non-optimized form), but classmap entries and the optimized/production autoloader both bake in a static list that goes stale the moment you add a class outside what was already scanned. After adding new classes, especially in a classmap-mapped directory or right before deploying with an optimized autoloader:
composer dump-autoload
Which Should You Use?
- PSR-4 — the default for all new code. Laravel, Symfony, and virtually every modern package use it exclusively.
- Classmap — only for legacy code you can't or don't want to restructure, or directories with non-class files mixed in that don't fit a namespace convention (like raw seed/migration scripts).
- files — for plain function libraries with no classes at all, loaded unconditionally on every request; keep this list minimal.
Untangling a messy or legacy PHP autoloading setup? Get in touch — I work as a freelance PHP developer.