Autoloading — the mechanism that makes require() unnecessary
Concept
Autoloading — the mechanism that automatically loads a PHP class file the first time the class name is used, without explicit require or include statements.
Without autoloading: Every PHP file that uses User must require 'User.php'. For large projects with hundreds of classes, this is unmaintainable.
PHP's autoload mechanism:
- When PHP encounters an undefined class, it calls all registered autoload functions.
- Each autoload function receives the fully-qualified class name as a string.
- The function maps the class name to a file path and
requires it. - If the file is loaded and the class is now defined, execution continues.
spl_autoload_register(callable $fn): Registers an autoload function. Multiple autoloaders can coexist.
PSR-4: The standard for PHP autoloading (PSR = PHP Standards Recommendation). Maps a namespace prefix to a base directory. App\Models\User → src/Models/User.php. Composer implements PSR-4.
Composer's autoloader:
composer.json:"autoload": {"psr-4": {"App\\": "app/"}}.composer dump-autoloadgeneratesvendor/autoload.php.require 'vendor/autoload.php'loads the autoloader.- Now any class in
app/matching theApp\namespace is auto-required.
Class maps: An alternative to PSR-4 where Composer scans directories and builds a class → file map at dump time. Faster at runtime (hash map lookup vs string manipulation), but requires dump-autoload when files change.
OPcache + autoloading: With OPcache, the require on first use caches the compiled file. Subsequent uses skip the file load entirely. opcache.preload can pre-require all classes before any request.
Code Example
<?php
// Without autoloading — explicit requires
require 'app/Models/User.php';
require 'app/Services/OrderService.php';
require 'app/Repositories/OrderRepository.php';
// 300 classes = 300 require statements — unmaintainable!
// Manual PSR-4 autoloader (what Composer does for you)
spl_autoload_register(function (string $className): void {
// 'App\Models\User' → 'app/Models/User.php'
$prefix = 'App\\';
$baseDir = __DIR__ . '/app/';
if (!str_starts_with($className, $prefix)) return; // not our namespace
$relativeClass = substr($className, strlen($prefix)); // 'Models\User'
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php'; // 'app/Models/User.php'
if (file_exists($file)) require $file;
});
// Now this works without any explicit require:
$user = new \App\Models\User(); // autoloader loads app/Models/User.php on demand
// composer.json
/*
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Tests\\": "tests/"
},
"classmap": ["database/seeders", "database/factories"],
"files": ["app/helpers.php"] // loaded on every request (for global functions)
}
}
*/
// After adding a new class or changing namespaces:
// composer dump-autoload
// This regenerates vendor/composer/autoload_psr4.php
// In public/index.php (and all entry points):
require __DIR__ . '/../vendor/autoload.php'; // one line — all classes available
// Checking if a class is autoloadable without loading it
$autoloader = require 'vendor/autoload.php';
$classmap = $autoloader->getClassMap();
echo isset($classmap[\App\Models\User::class]) ? 'found in classmap' : 'PSR-4 lookup';
// Laravel's optimize:
// php artisan optimize → generates bootstrap/cache/compiled.php (classmap-like)
// Faster than PSR-4 string manipulation at runtime