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

Book

relative to the current namespace (Library\Catalogue\Book), or an import

qualified

Catalogue\Book

relative to the current namespace, or the first segment is an import

fully qualified

\Library\Catalogue\Book

exactly as written

relative (namespace\)

namespace\Book

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.

Name resolution rules

The manual’s resolution rules, summarised:

  1. Fully qualified names (leading \) are used as written.

  2. For a qualified name, if the first segment matches a class/namespace import, it is replaced; otherwise the current namespace is prepended.

  3. For an unqualified class name, a matching class import is used; otherwise the current namespace is prepended. No fallback.

  4. For an unqualified function or constant name, a matching use function / use const import is used; otherwise the current namespace is tried first and then the global namespace (fallback).

  5. Names in strings are always fully qualified.

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

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.

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

::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

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; use spl_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_once for every class is unnecessary with autoloading; keep require for the autoloader itself and for configuration files.

  • Group use (use A\{B, C};, 7.0) and use 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).