skip to content

Under PSR-4, with the prefix Acme\Blog\ mapped to src/, which file must hold Acme\Blog\Http\PostController, and what must match?

level: middleimportance: must knowfreq 62%

answer

  1. prefix maps to a base directory
  2. remaining segments become subdirectories
  3. class name plus .php
  4. case must match exactly
  5. underscores mean nothing in PSR-4

basics

~10 s

The file is src/Http/PostController.php: the prefix Acme\Blog\ is replaced by src/, remaining namespace segments become directories and the class name becomes the file name, all matching the class name's letter case.

solid answer

~40 s

PSR-4 maps a **namespace prefix** to a **base directory**. For `Acme\Blog\Http\PostController` with `Acme\Blog\` mapped to `src/`, the autoloader strips the prefix, turns the remaining sub-namespaces into subdirectories and the terminating class name into a `.php` file name: `src/Http/PostController.php`. The standard requires directory names to match the case of the sub-namespace names and the file name to match the class name's case, because the path is built from the name exactly as written. Underscores have no special meaning (unlike the deprecated PSR-0, which turned them into directory separators). PSR-4 also says an autoloader must not throw exceptions or raise errors, so it simply returns when it has no file. In practice this implies one class per file, placed where its name says.

code

php · 16 lines
php
<?php
declare(strict_types=1);

// PSR-4: prefix Acme\Blog\ -> base directory src/
function psr4Path(string $class): ?string
{
    $prefix = 'Acme\\Blog\\';
    if (!str_starts_with($class, $prefix)) {
        return null;
    }
    $relative = str_replace('\\', '/', substr($class, strlen($prefix)));
    return 'src/' . $relative . '.php';
}

var_dump(psr4Path('Acme\Blog\Http\PostController')); // "src/Http/PostController.php"
var_dump(psr4Path('Acme\Log\File_Writer'));           // NULL: prefix not handled here

go deeper

for a junior

Be able to map a class name to its file: strip the prefix, turn namespace segments into folders, add .php to the class name.

for a middle

Explain the prefix-to-base-directory rule, why case must match, and why the autoloader must return silently instead of throwing.

for a senior

Use the one-class-per-file rule to spot order-dependent loading bugs, and keep namespace prefixes aligned with directory roots across packages.

for a principal

Choose namespace prefixes and directory roots as a stable public contract of each package, since renaming either breaks every consumer's autoloading.

## What PSR-4 is **PSR-4** is a PHP-FIG standard that describes how an autoloader turns a fully qualified class name into a file path. PHP itself enforces nothing about file layout; PSR-4 is the shared convention that lets one autoloader, usually Composer's, load classes from many libraries. The term "class" in the standard covers classes, interfaces, traits and similar structures, which today includes enums. ## The mapping rule A fully qualified class name has the form `\Vendor\SubNamespace...\ClassName`. PSR-4 splits it into three parts: 1. A **namespace prefix**: one or more leading segments, such as `Acme\Blog`. It must include at least the top-level vendor namespace. 2. The **remaining sub-namespaces**, such as `Http`, which become subdirectories under the base directory, with namespace separators turned into directory separators. 3. The **terminating class name**, such as `PostController`, which becomes the file name with a `.php` extension. The prefix maps to one or more **base directories**. Worked through: | Class | Prefix | Base directory | File | |---|---|---|---| | `Acme\Blog\Http\PostController` | `Acme\Blog\` | `src/` | `src/Http/PostController.php` | | `Acme\Blog\Post` | `Acme\Blog\` | `src/` | `src/Post.php` | | `Acme\Blog\Tests\PostTest` | `Acme\Blog\Tests\` | `tests/` | `tests/PostTest.php` | | `Acme\Log\File_Writer` | `Acme\Log\` | `lib/` | `lib/File_Writer.php` | The last row shows that **underscores are ordinary characters** in PSR-4. The older PSR-0 converted `_` in class names into directory separators; PSR-0 was deprecated by PHP-FIG in 2014 in favour of PSR-4. When several prefixes match, an autoloader typically tries the longest one first, so `Acme\Blog\Tests\` wins over `Acme\Blog\` for test classes. ## Case sensitivity PHP treats class names case-insensitively: once `PostController` is loaded, `new postcontroller()` finds it. File systems are another matter. PSR-4 therefore states that class names must be referenced in a case-sensitive fashion and that directory and file names must **match the case** of the namespace segments and class name. Because the autoloader builds the path from the name exactly as it appears in the code, a reference spelled with different case produces a different path, which fails on case-sensitive file systems. ## What the autoloader must not do PSR-4 includes rules for the autoloader itself: - It **must not throw exceptions** and **must not raise errors** of any level. - It **should not return a value**. The reason is the autoloader queue: if one autoloader cannot find a file for a name, another one registered after it may be responsible. Throwing would stop PHP from asking the others, and `class_exists()` checks would blow up instead of returning `false`. The correct behaviour for an unknown name is to return silently. ## Checklist for a PSR-4 layout - The namespace prefix in the autoloader configuration ends with a namespace separator, for example `Acme\Blog\`. - The base directory is resolved from the project root, not from the working directory. - Every directory under the base mirrors one namespace segment, with identical case. - Every file holds exactly one class-like declaration named after the file. - Each file's `namespace` line equals the prefix plus the directory path. - Test classes use their own prefix, commonly mapped to a separate `tests/` directory. - Nothing in the class name relies on underscores to express hierarchy. ## One class per file PSR-4 maps each class name to exactly one file, which implies that every class, interface, trait or enum lives in **its own file**, named after it. Putting a helper class next to another class in the same file breaks this: the helper can only be found if the other class's file happened to be loaded first, a classic order-dependent bug. PSR-1 states the same expectation from the coding-style side. ## A minimal PSR-4 loader ```php <?php declare(strict_types=1); spl_autoload_register(static function (string $class): void { $prefix = 'Acme\\Blog\\'; if (!str_starts_with($class, $prefix)) { return; // not ours: let other autoloaders try } $relative = substr($class, strlen($prefix)); $file = __DIR__ . '/src/' . str_replace('\\', '/', $relative) . '.php'; if (is_file($file)) { require $file; } }); ``` Composer's generated loader applies the same rule, with the prefix-to-directory table coming from project configuration.

  • How does PSR-0 treat Acme_Blog_Post differently from PSR-4?
    PSR-0 converts each underscore in the class name into a directory separator, so `Acme_Blog_Post` maps to `Acme/Blog/Post.php`, a leftover from pre-namespace naming. PSR-4 gives underscores no special meaning, so the file would be `Acme_Blog_Post.php` under the base directory. PSR-0 has been deprecated since 2014; PSR-4 is the recommended standard.
  • Under PSR-4, why must an autoloader return silently for a class it cannot find?
    PHP may have several autoloaders registered, and a class unknown to one may belong to another. PSR-4 therefore forbids throwing exceptions or raising errors: throwing would stop PHP from calling the remaining autoloaders, and a `class_exists()` check would fail with an exception instead of returning `false`.

saying these in an interview costs you the question

  • PSR-4 turns underscores in class names into directories
  • File and directory case does not matter because PHP class names are case-insensitive
  • PHP itself enforces the PSR-4 directory layout
  • A PSR-4 autoloader should throw when it cannot find a file
  • Several classes per file are fine under PSR-4 as long as one matches the file name