How PHP Autoloading Works with PSR-4 and Composer
PSR-4 maps PHP namespaces to directory structures, and Composer generates the autoloader that enforces this mapping—learn how they work together.
How PHP Autoloading Works with PSR-4 and Composer
PHP fatals with a class-not-found error. The fix is not another require_once. It is PSR-4 autoloading with Composer: a specification defines the mapping rules, and Composer generates the loader that enforces them. PSR-4, published by PHP-FIG, is the contract. Composer writes the autoloader. You include it once in a bootstrap file and never touch it again.
Treating PSR-4 and Composer as the same thing causes most of the confusion. They are not. PSR-4 states that a namespace prefix maps to a base directory, and every sub-namespace maps to a subdirectory of the same name. Composer implements that: it reads the mapping from your composer.json, resolves it against the filesystem, and dumps vendor/autoload.php, which registers the mapping with spl_autoload_register. The generated loader builds a static array of prefix-to-directory pairs. No filesystem scan on every request.
This split matters when you debug. If your class is not found, ask three questions. Is the file in the right directory per PSR-4? Did you run composer dump-autoload after adding it? Does the namespace prefix in your composer.json autoload section match the namespace declaration in the file? Most failures are a mismatch on the third question, not a bug in Composer.
Composer Autoloader Explained
How The Loader Works Under The Hood
Composer generates an autoloader that handles four distinct loading strategies. Which one activates depends on how a package or your own code is declared. Require vendor/autoload.php once in your application entry point. That file includes vendor/composer/autoload_real.php, which returns a singleton ClassLoader instance configured with every rule from your composer.json and every package you have installed.
The ClassLoader object holds four internal maps. PSR-4 prefixes point to an array of directories. PSR-0 prefixes, kept for legacy support, work the same way but with the older underscore-to-directory rule. The classmap is a flat array of fully qualified class names to absolute file paths, built by scanning directories you specify. The files entry is a list of paths included on every request, used for functions or constants that do not follow a class-file convention.
Lookup Order And Performance
When PHP encounters an undefined class, the registered autoloader checks PSR-4 first, then PSR-0, then the classmap, and finally falls through to an include if the file is found. The order is hardcoded. You cannot change it without forking the Composer source, but you rarely need to. The classmap lookup is a single array key check. That makes it the fastest path. The PSR-4 lookup does a prefix match against the registered directories and then builds a file path from the remaining namespace segments.
The loader registers itself with Composer\Autoload\includeFile, a wrapper around PHP's include that tracks what was loaded and suppresses duplicate includes. Call class_exists with the autoload flag set to false, and the loader is bypassed entirely. Frameworks use this to check for optional dependencies without triggering a missing-class fatal.
PSR-4 Namespace Directory Mapping
The Single Rule That Governs Everything
PSR-4 is one rule. A fully qualified namespace prefix is stripped from the class name. The remainder is converted to a relative file path by replacing namespace separators with directory separators and appending .php. The prefix maps to a base directory on disk. A class named Acme\Blog\PostController with the prefix Acme\Blog mapped to src/ resolves to src/PostController.php. That is the entire specification.
The spec does not mandate a composer.json format. It does not define autoload-dev versus autoload sections. It does not specify how the mapping is cached or optimised. Those are Composer concerns. PSR-4 only requires that a conforming implementation, given a class name, can locate the corresponding file using this algorithm. The PHP-FIG document is under three pages long. The normative part is a single code block.
Multiple Base Directories And The Most Common Mistake
Multiple base directories per prefix are allowed and evaluated in registration order. Composer supports this: map a prefix to an array of directories, and the loader tries the first, then the second if the file does not exist. Packages can ship with a src/ directory for production classes and a tests/ directory for test doubles that share the same namespace. A separate autoload-dev entry for tests is cleaner.
The most common mapping mistake is forgetting that the base directory is the directory that contains the namespace root, not the directory that shares the namespace name. If your namespace is App and your classes live in src/App/, the base directory is src/, not src/App/. Mapping App to src/App/ means the loader looks for src/App/Controller.php when you reference App\Controller. The file is actually at src/App/Controller.php. You get a doubled path segment and a silent class-not-found.
PHP Class Autoloading Standard
PSR-4 Versus The Deprecated PSR-0
The PHP autoloading landscape has exactly two living PSRs: PSR-4 and the deprecated PSR-0. PSR-0 was the original, written when PHP 5.2 namespaces were new and many projects still used the PEAR underscore convention. It maps underscores in class names to directory separators, so Net_HTTP_Client becomes Net/HTTP/Client.php. PSR-4 drops the underscore rule and simplifies the mapping to namespace segments only. If you are starting a project after 2014, PSR-4 is the only choice.
How spl_autoload_register Works
PHP's autoloading mechanism is a single function: spl_autoload_register. Pass it a callable that accepts a class name, and PHP invokes it whenever an undefined class is referenced. You can register multiple loaders in a stack. Composer's loader registers one. Frameworks sometimes register additional loaders for cached compiled class maps or plugin discovery. Registration order matters. The first loader that finds a file wins.
The default spl_autoload implementation, which predates the SPL registration system, maps underscores to directory separators and appends a file extension from a list you can configure with spl_autoload_extensions. That default covers .inc and .php. No modern project should rely on it. It exists for backward compatibility with code written before 2005, and its presence signals legacy code untouched in a decade.
A PSR-4 compliant loader must implement the interface defined in the PSR-4 meta document. Composer's ClassLoader does not formally declare that it implements this interface, but it conforms in behaviour. The conformance test suite published by PHP-FIG verifies that an implementation resolves class names correctly across edge cases: trailing namespace separators, empty namespace prefixes, and case-sensitive filesystems. Composer passes this suite. Its behaviour on those edge cases is deterministic.
Classmap vs PSR-4 Autoloading
Runtime Speed Versus Maintenance Cost
Classmap loading and PSR-4 loading solve the same problem with different trade-offs. A classmap is a generated PHP file containing a single associative array of fully qualified class names to absolute file paths. Composer builds it by scanning every PHP file in the directories you specify, extracting class, interface, and trait declarations, and writing the map to vendor/composer/autoload_classmap.php. Class lookup is an O(1) array access with no filesystem interaction. It is the fastest path at runtime.
PSR-4 does no upfront scanning. It determines the file path at runtime by stripping the namespace prefix and converting the remainder to a directory path. That calculation is cheap, but the loader must still check whether the file exists before including it. On a production server with a warm opcache, the file-exists check is a stat cache hit and costs nearly nothing. On a development machine with a cold filesystem cache, the stat calls add up across hundreds of classes.
The real difference is maintenance. A classmap must be regenerated every time you add, remove, or rename a class. Forget, and the class is not found. The error message does not point to the classmap as the cause. PSR-4 requires no regeneration. Drop a new file into the right directory with the right namespace, and it loads on the next request. This is why PSR-4 is the default for application code. Classmap is reserved for packages that do not follow PSR-4 or for the final production optimisation step.
When To Switch To An Optimised Classmap
Composer uses classmap internally for its own classes and for any package whose authors did not declare a PSR-4 or PSR-0 rule. You can force a classmap for a directory by adding it to the classmap array in your composer.json autoload section. You can also generate an optimised loader with composer dump-autoload --optimize. This builds a classmap for every class in the project, even those covered by PSR-4 rules, and prefers the classmap lookup over runtime path resolution. The trade-off is a larger vendor/composer directory and the requirement to regenerate on every class change.
Start with PSR-4 for every project. Switch to an optimised classmap only when you have profiled the loader and found it taking measurable time in production. With opcache and a modern PHP 8.x runtime, the PSR-4 path resolution is rarely the bottleneck. The filesystem stat calls that classmap eliminates are absorbed by the opcache file cache.
Common Questions
What is the difference between PSR-4 and Composer autoloading?
PSR-4 is the specification that defines how a namespace maps to a directory structure. Composer is the tool that reads your composer.json, resolves the mappings, and generates the concrete loader file you include in your project. The specification is a document. The loader is the generated code.
Do I need to run composer dump-autoload after adding a new class?
Not for PSR-4 loading. The runtime path resolution finds new files automatically. You must run it after changing the autoload section of composer.json, after adding a class to a classmap directory, or after adding a file to the files loading array.
What does vendor/autoload.php actually do?
It requires the generated ClassLoader instance, configures it with every namespace prefix, classmap entry, and files list from your project and its dependencies, and registers it with spl_autoload_register. After that single require, any class in the dependency graph loads on demand.
Can I use PSR-4 without Composer?
Yes. PSR-4 is a specification independent of any tool. Write your own loader that implements the namespace-to-directory mapping algorithm and register it with spl_autoload_register. Composer is the most common implementation, but it is not the only one.
Why does my PSR-4 loading fail after moving a file?
The most likely cause is a namespace declaration that does not match the new directory path. PSR-4 requires that the namespace segment after the registered prefix matches the subdirectory name exactly. A second common cause is a case mismatch on a case-sensitive filesystem, typically when developing on macOS and deploying to Linux.
What is the files autoloading entry for?
It is for PHP files that define functions or constants, not classes. These files are included on every request, before any class loading happens. Use it sparingly. Every file listed adds to the per-request include cost with no lazy-loading benefit.
Should I use classmap or PSR-4 for my application code?
Start with PSR-4. It requires zero regeneration and the runtime cost is negligible under PHP 8.x with opcache enabled. Switch to an optimised classmap only if profiling shows the loader taking more than 5 millisecods per request in production, which is uncommon.