Composer Autoloading
The autoloading page covered spl_autoload_register() and showed how a hand-written callback can turn a namespaced class name into a file path automatically. In practice, almost no PHP project writes that callback itself anymore. Composer, the standard PHP dependency manager, generates a complete autoloader for every project it touches, built on that same spl_autoload_register() mechanism underneath, but driven entirely by declarative configuration instead of hand-written resolution logic. This page is about what Composer's generated autoloader actually contains, how its mapping is declared, and the handful of situations where PSR-4 alone isn't enough.
What vendor/autoload.php actually is
Any project managed by Composer has a vendor/ directory, and inside it a file called autoload.php. Requiring that one file at the top of an entry point is, in the overwhelming majority of PHP projects today, the entire autoloading setup — no other require statements needed anywhere else in the codebase.
public/index.php — the only require a Composer project needs
<?php
require __DIR__ . '/../vendor/autoload.php';
use App\Services\Mailer;
$mailer = new Mailer();
$mailer->send('team@example.com', 'Deploy finished.');That single file doesn't contain the autoloading logic itself so much as it bootstraps it. Opening vendor/autoload.php shows it's tiny: it requires vendor/composer/autoload_real.php, which in turn registers a class loader and calls spl_autoload_register() on it, exactly like the hand-written example from the previous page — just generated, optimized, and kept in sync automatically instead of maintained by a developer. The real work lives in a set of generated files under vendor/composer/, including lookup tables like autoload_psr4.php and autoload_classmap.php that map prefixes and class names to the exact files that define them. None of those generated files should ever be edited by hand — they get regenerated wholesale every time Composer's autoloader is rebuilt, and any manual edit is silently thrown away the next time that happens.
Declaring the PSR-4 mapping in composer.json
The mapping that decides which namespace prefixes live in which directories isn't inferred by scanning the filesystem — it's declared explicitly in composer.json, under an autoload key with a psr-4 section. Each entry maps a namespace prefix to the directory that acts as its root.
composer.json — mapping the App namespace to src/
{
"name": "acme/inventory",
"require": {
"monolog/monolog": "^3.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}With that mapping in place, a class declared as App\Services\Mailer is expected to live at src/Services/Mailer.php, and Composer's generated autoloader knows to look there the first time that class name is referenced. The mapping can list more than one prefix, which is common in projects that separate application code from test code under different namespaces, each pointing at its own directory — App\\ at src/ and App\\Tests\\ at tests/, for instance.
Regenerating the autoloader with dump-autoload
Composer doesn't rescan the filesystem on every single request to figure out where classes live — that would be far too slow for production. Instead, it builds a static lookup table once and writes it to the files under vendor/composer/. That means the lookup table has to be rebuilt whenever the set of classes changes in a way the existing table doesn't already account for, and the command that does that rebuild is composer dump-autoload.
Regenerating the autoloader after adding classes
composer dump-autoload
Generating autoload files Generated autoload files containing 4 classes
For plain PSR-4 mappings this is often not even necessary in practice, because Composer's default PSR-4 loader falls back to deriving the expected file path from the class name at runtime if it isn't already in the lookup table, then caches the result. Where dump-autoload becomes necessary is classmap and files autoloading (covered below), since those rely on an explicit list built by scanning the filesystem at generation time — nothing is inferred at runtime for either of those. It's also worth running whenever autoloading behavior looks stale or wrong, since it's a cheap, side-effect-free way to force a full rebuild.
This is also why composer install and composer update regenerate the autoloader automatically as one of their final steps — both commands can change which packages are present and what those packages declare in their own autoload sections, so leaving the old lookup table in place afterward would risk it going stale immediately. A developer only needs to run dump-autoload by hand in the narrower case of adding classmap or files entries, or new classes under an existing PSR-4 prefix, without otherwise touching composer.json's dependency list.
Classmap and files autoloading for legacy code
PSR-4 assumes a clean, consistent relationship between namespace and directory structure. Plenty of real code doesn't have that — a directory of old, loosely organized class files with no consistent namespace, or a functions.php full of global helper functions that was never wrapped in a class at all. Composer supports both cases directly, without requiring the legacy code to be restructured first.
composer.json — classmap and files alongside psr-4
{
"autoload": {
"psr-4": {
"App\\": "src/"
},
"classmap": [
"legacy/classes/"
],
"files": [
"legacy/functions.php"
]
}
}classmap tells Composer to scan the listed directories (or individual files) for every class, interface, and trait declaration it can find, and record each one's exact location in the generated lookup table — regardless of what namespace it uses or how its directory structure is organized. This is the tool for adopting Composer autoloading in an existing codebase that predates any namespace convention, without renaming or moving a single file.
files is different in kind: it isn't autoloading in the class-lookup sense at all, since there's no class name to resolve. Instead, every file listed under files is unconditionally required on every single request, right when vendor/autoload.php runs. This is the mechanism for pulling in a file of loose, global functions that need to exist everywhere, since a function can't be autoloaded the way a class can — PHP has no spl_autoload_register() equivalent that triggers on an undefined function call.
Why this replaced hand-rolled autoloaders everywhere
Writing a spl_autoload_register() callback by hand, as shown on the autoloading page, is a genuinely useful exercise for understanding the mechanism, but it duplicates work that has to be redone for every dependency a project pulls in — a hand-written autoloader only knows about the namespaces its author explicitly coded for. The moment a project depends on even one third-party package, that package brings its own classes, its own namespace, and its own expectations about where its files live. Composer's autoloader solves this once, for every package simultaneously, by merging the autoload section from the project's own composer.json with the autoload section declared inside every installed dependency's composer.json, producing one unified lookup table that covers the entire dependency tree. That's the concrete reason virtually every modern PHP project — from a small script that depends on a single library to a full framework application — uses Composer's generated autoloader rather than assembling its own spl_autoload_register() calls, and it's also why installing any package via composer require is enough on its own to make that package's classes available with no additional wiring.
vendor/autoload.phpis the single file a Composer-managed project requires; it bootstraps a generated class loader built onspl_autoload_register().The
autoload.psr-4section ofcomposer.jsondeclares which namespace prefixes map to which directories.composer dump-autoloadrebuilds the generated lookup table;composer installandcomposer updatedo this automatically because installed packages can change.classmapautoloads legacy classes that don't follow PSR-4 by scanning listed directories for class declarations.filesunconditionally requires listed files on every request — the mechanism for global helper functions, which can't be autoloaded like classes.Classmap and files entries are baked in at generation time and need a fresh
dump-autoloadafter any change to the classes or functions they cover.Composer autoloading replaced hand-rolled
spl_autoload_register()setups because it merges the autoload mapping of every installed dependency automatically.