Namespaces and Autoloading
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against PHP 8.5, checked on 2026-10-05. Namespaces (5.3) give classes, interfaces, traits,
enums, functions and constants qualified names, so that code from different libraries can coexist; autoloading loads
the file that defines a class the first time it is used, so applications never write require for their own classes.
Together with Composer and the PSR-4 standard, they define how modern PHP projects are laid out.
Declaring a namespace
namespace Library\Catalogue; at the top of a file (after declare, before any other code) puts every class,
interface, trait, enum, function and constant declared in the file into that namespace. Namespaces are hierarchical
by convention (Vendor\Package\SubArea) but not by semantics: Library\Catalogue has no special relationship with
Library.
<?php
declare(strict_types=1);
namespace Library\Catalogue;
const MAX_COPIES = 5;
function normaliseIsbn(string $isbn): string
{
return str_replace('-', '', $isbn); // str_replace: falls back to the global function
}
final class Book
{
public function __construct(public string $isbn) {}
}
$book = new Book(normaliseIsbn('978-0-00-000001-1'));
echo Book::class, "\n"; // Library\Catalogue\Book
echo __NAMESPACE__, "\n"; // Library\Catalogue
echo \Library\Catalogue\MAX_COPIES, ' ', namespace\MAX_COPIES, "\n"; // 5 5 -- the namespace keyword
echo $book->isbn, "\n";
A file may declare several namespaces, either with consecutive namespace X; statements or, preferably, with braces:
namespace X { … } namespace { … } (the unnamed one being the global namespace). One namespace per file, matching its
directory, is the standard practice.
Kinds of names
| Kind | Example | Resolved as |
|---|---|---|
unqualified |
|
relative to the current namespace ( |
qualified |
|
relative to the current namespace, or the first segment is an import |
fully qualified |
|
exactly as written |
relative ( |
|
the current namespace, explicitly |
Strings are always fully qualified, with or without the leading backslash: new $className(), class_exists('…'),
callables and constant('…') never see the current namespace or its imports. Use Book::class to build such strings
safely.
Importing with use
use imports names into the current file, optionally under an alias. Separate forms import functions and constants.
Imports are per file and resolved at compile time.
<?php
declare(strict_types=1);
namespace Library\Catalogue {
final class Book { public function __construct(public string $title) {} }
final class Author { public function __construct(public string $name) {} }
function slug(string $s): string { return strtolower(str_replace(' ', '-', $s)); }
const DEFAULT_SHELF = 'A';
}
namespace Library\Loans {
final class Loan { public function __construct(public string $title) {} }
}
namespace App {
use Library\Catalogue\Book; // class
use Library\Catalogue\{Author, Book as Title}; // group use, with an alias
use Library\Loans\Loan as Borrowing; // alias to avoid a clash or improve clarity
use function Library\Catalogue\slug; // function
use const Library\Catalogue\DEFAULT_SHELF; // constant
$book = new Book('The Dispossessed');
$same = new Title('The Dispossessed');
echo slug($book->title), ' ', DEFAULT_SHELF, ' ', get_class(new Borrowing('x')), ' ', $same::class, "\n";
}
the-dispossessed A Library\Loans\Loan Library\Catalogue\Book
A use ... as alias exists only in the file that declares it. class_alias() creates a global alias at run time:
after class_alias(Loan::class, 'Borrowing'), new Borrowing builds a Library\Loans\Loan and get_class()
reports the real name. Libraries use it to keep an old class name working after a rename.
The global fallback
When code in a namespace uses an unqualified function or constant name that does not exist in the current
namespace, PHP falls back to the global one: strlen() inside Library\Catalogue finds \strlen(). Classes do
not fall back: new DateTimeImmutable() inside a namespace looks for Library\Catalogue\DateTimeImmutable and fails,
so global classes must be imported (use DateTimeImmutable;) or written fully qualified (\DateTimeImmutable).
<?php
declare(strict_types=1);
namespace Library\Catalogue;
use DateTimeImmutable; // import the global class...
echo strlen('Kindred'), "\n"; // functions fall back to global: 7
echo PHP_VERSION_ID >= 80500 ? "8.5+\n" : "older\n"; // constants fall back too
$now = new DateTimeImmutable('2026-10-05'); // ...because classes do not fall back
echo $now->format('Y'), "\n";
try {
$class = 'ArrayObject';
var_dump(new $class() instanceof \ArrayObject); // strings are fully qualified: works
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}
Some code bases write \strlen() or use function strlen; to skip the fallback lookup; OPcache can then inline some
internal functions. The gain is small, and coding standards differ on whether to do it.
Source: Global space fallback and Global space
Name resolution rules
The manual’s resolution rules, summarised:
-
Fully qualified names (leading
\) are used as written. -
For a qualified name, if the first segment matches a class/namespace import, it is replaced; otherwise the current namespace is prepended.
-
For an unqualified class name, a matching class import is used; otherwise the current namespace is prepended. No fallback.
-
For an unqualified function or constant name, a matching
use function/use constimport is used; otherwise the current namespace is tried first and then the global namespace (fallback). -
Names in strings are always fully qualified.
Source: Name resolution rules
Autoloading with spl_autoload_register
When code uses a class, interface, trait or enum that is not defined yet, PHP calls the registered autoloaders with
the fully qualified class name; an autoloader is expected to require the file that defines it.
spl_autoload_register() adds an autoloader to a queue (several libraries can each register one). Autoloaders are not
used for functions and constants.
<?php
declare(strict_types=1);
// Create a tiny source tree to autoload from (normally it already exists).
$base = sys_get_temp_dir() . '/autoload-demo/src';
@mkdir($base . '/Catalogue', 0777, true);
file_put_contents($base . '/Catalogue/Book.php', <<<'PHP'
<?php
declare(strict_types=1);
namespace Library\Catalogue;
final class Book { public function __construct(public string $title) {} }
PHP);
// A minimal PSR-4 autoloader: prefix "Library\" maps to the src/ directory.
spl_autoload_register(static function (string $class) use ($base): void {
$prefix = 'Library\\';
if (!str_starts_with($class, $prefix)) {
return; // not ours: let the next autoloader try
}
$relative = substr($class, strlen($prefix)); // "Catalogue\Book"
$file = $base . '/' . str_replace('\\', '/', $relative) . '.php';
if (is_file($file)) {
require $file;
}
});
$book = new Library\Catalogue\Book('Kindred'); // triggers the autoloader
echo $book->title, "\n"; // Kindred
var_dump(class_exists('Library\Catalogue\Missing')); // bool(false): autoloader ran, found nothing
var_dump(class_exists('Library\Catalogue\Other', false)); // bool(false): false = do not autoload
Source: Autoloading Classes and spl_autoload_register
PSR-4 and Composer’s autoloader
PSR-4, the PHP-FIG autoloading standard, maps a namespace prefix to a base directory: the rest of the class name
becomes the path, with \ turned into / and .php appended. Library\Catalogue\Book with the prefix Library\
mapped to src/ lives in src/Catalogue/Book.php. File and directory names are case-sensitive and must match the
class name exactly.
Nobody writes this autoloader by hand: Composer generates one from composer.json. A project with the layout
library-catalogue/
├── composer.json
├── bin/catalogue.php
└── src/
├── functions.php
└── Catalogue/
└── Book.php namespace Library\Catalogue; final readonly class Book
declares the mapping (and a file of namespaced functions, which autoloading cannot load lazily):
{
"name": "acme/library-catalogue",
"type": "project",
"require": {
"php": ">=8.5"
},
"autoload": {
"psr-4": {
"Library\\": "src/"
},
"files": ["src/functions.php"]
}
}
and every entry point requires the generated autoloader once:
<?php
declare(strict_types=1);
// bin/catalogue.php
require __DIR__ . '/../vendor/autoload.php';
use Library\Catalogue\Book;
use function Library\slug;
$book = new Book('The Left Hand of Darkness', 1969);
echo slug($book->title), "\n"; // the-left-hand-of-darkness
composer dump-autoload # regenerate vendor/autoload.php after changing "autoload"
php bin/catalogue.php
composer dump-autoload --optimize # production: also build a classmap of every class (faster)
autoload-dev holds mappings needed only in development (tests). Besides psr-4, Composer supports classmap
(scan directories and map every class found, for code that does not follow PSR-4) and files (always include). See
Composer and Packages for optimised and authoritative
classmaps.
Source: PSR-4: Autoloader and Composer schema: autoload
When autoloading runs, and why a class is not found
An autoloader is called only when PHP needs the definition of a class that is not loaded yet. Many constructs that name a class do not need it, which is why a typo in them goes unnoticed. A logging autoloader shows which ones do:
<?php
declare(strict_types=1);
namespace Library;
spl_autoload_register(function (string $class): void {
echo " autoload($class)\n";
});
function check(string $label, callable $code): void
{
echo $label, "\n";
try {
$code();
} catch (\Error $e) {
echo ' ', get_class($e), ': ', $e->getMessage(), "\n";
}
}
check('Missing::class', fn () => print(' ' . Missing::class . "\n"));
check('instanceof', fn () => var_dump(new \stdClass() instanceof Missing));
check('class_exists($name, false)', fn () => var_dump(class_exists(Missing::class, false)));
check('class_exists($name)', fn () => var_dump(class_exists(Missing::class)));
check('a type declaration', function () { $f = fn (?Missing $m = null) => 'called'; echo ' ', $f(), "\n"; });
check('catch (Missing)', function () { try { throw new \RuntimeException('x'); } catch (Missing | \RuntimeException) { echo " caught\n"; } });
check('new', fn () => new Missing());
check('a static call', fn () => Missing::make());
Missing::class
Library\Missing
instanceof
bool(false)
class_exists($name, false)
bool(false)
class_exists($name)
autoload(Library\Missing)
bool(false)
a type declaration
called
catch (Missing)
caught
new
autoload(Library\Missing)
Error: Class "Library\Missing" not found
a static call
autoload(Library\Missing)
Error: Class "Library\Missing" not found
Source: Autoloading Classes, class_exists and Type operators
::class is resolved at compile time from the namespace and use statements, so it is a string even for a class
that does not exist. instanceof, type declarations and catch compare against a name without loading anything: an
object cannot be an instance of a class that was never loaded. Only code that needs the class itself (new, a static
call or constant, extends, class_exists() without false) calls the autoloader, and an Error follows when no
autoloader defines the class. Static analysers (Static
Analysis and Refactoring) report the silent cases.
The other common cause of Class "..." not found is case. Class names are case-insensitive in PHP, but PSR-4 turns
them into file paths, and Linux file systems are case-sensitive. In the Composer project above (without the files
entry), with Book.php correct and a second class Loan saved as src/Catalogue/loan.php:
<?php
declare(strict_types=1);
// bin/case.php
require __DIR__ . '/../vendor/autoload.php';
foreach (['Library\catalogue\book', 'Library\Catalogue\Book', 'Library\catalogue\book', 'Library\Catalogue\Loan'] as $name) {
try {
echo $name, ': ', (new ReflectionClass($name))->getName(), "\n";
} catch (ReflectionException $e) {
echo $name, ': ', $e->getMessage(), "\n";
}
}
$ composer dump-autoload && php bin/case.php
Library\catalogue\book: Class "Library\catalogue\book" does not exist
Library\Catalogue\Book: Library\Catalogue\Book
Library\catalogue\book: Library\Catalogue\Book
Library\Catalogue\Loan: Class "Library\Catalogue\Loan" does not exist
$ composer dump-autoload --optimize --strict-psr; echo "exit $?"
Generating optimized autoload files
Class Library\Catalogue\Loan located in ./src/Catalogue/loan.php does not comply with psr-4 autoloading standard (rule: Library\ => ./src). Skipping.
Generated optimized autoload files containing 2 classes
exit 1
Source: Composer — dump-autoload and PSR-4: Autoloader
The lowercase name failed the first time, because the autoloader looked for src/catalogue/book.php, and worked the
second time, because by then the class was loaded under its real name and PHP compared names case-insensitively. The
same code passes on macOS or Windows, whose file systems ignore case, and fails in a Linux container or CI, often
depending on which line ran first. Loan was never found: loan.php does not match the class name. Composer
reports such files when it builds an optimised classmap, and --strict-psr (with --optimize or
--classmap-authoritative) turns the report into a failing exit code, which makes it a useful CI step. These runs
used PHP 8.5.11 and Composer 2.10.3.
Where older books and tutorials are outdated
-
__autoload()(Kromann ch. 6) was deprecated in 7.2 and removed in 8.0; usespl_autoload_register(), or rather Composer’s autoloader. -
PSR-0 (with underscores as directory separators,
Zend_Db_Table) was deprecated in 2014 in favour of PSR-4; 2010-era framework code (the Wiley book) uses underscore "pseudo-namespaces". -
Manual
require_oncefor every class is unnecessary with autoloading; keeprequirefor the autoloader itself and for configuration files. -
Group use (
use A\{B, C};, 7.0) anduse function/use const(5.6) are often missing from older material. -
Namespaced names as single tokens: since PHP 8.0 a namespaced name is one token, so reserved keywords may appear as namespace segments (
App\List\Item).