# Namespaces

Source: https://codewiki.com/php/namespaces/

> - **what**: A namespace gives classes, interfaces, traits, enums, functions, and constants hierarchical names, so separate libraries can safely use the same short name.
> - **trap**: `use` only resolves names while compiling the current file; it does not load a file, and dynamic strings do not apply imports. Functions and constants also have a global fallback that class-like names lack.
> - **fix**: Declare the namespace and imports at the top of each file, pass `SomeType::class` when constructing dynamically, and keep Composer's PSR-4 mapping exact in name, path, and case.

## What it is and why it exists

A namespace is part of a declaration's name, not a runtime container. `App\Billing\Invoice` and `Vendor\Archive\Invoice` both have the short name `Invoice`, but they are different classes. A project can therefore combine its own code with third-party packages without inventing a prefix for every type.

Namespaces can contain classes, interfaces, traits, enums, functions, and constants. They do not isolate variables or establish file scope. Ordinary variables still follow PHP's existing variable-scope rules; a namespace changes only the resolution of supported declarations and their references.

You meet namespaces in nearly every modern PHP project. Framework components, Composer packages, test classes, and domain code all use complete names to distinguish ownership. Even a small entry script applies the same rules when it imports `DateTimeImmutable`, an exception type, or a library class.

Namespaces solve name collision and organization. Autoloading solves how PHP finds and loads a definition file when code needs a class-like name. PSR-4 often connects the two, but they are separate mechanisms: a correct `use` statement cannot repair a broken autoload mapping, and a correct mapping cannot repair the wrong class name.

## How it works

### Declarations and extent

The unbracketed form, `namespace App\Billing;`, applies from that point to the end of the file or the next namespace declaration. The bracketed form, `namespace App\Billing { ... }`, explicitly encloses code. One file cannot mix the two forms; production projects usually keep one namespace per file, while fixtures and demonstrations more often put several together.

A namespace declaration must be the first statement in its file, although `declare` may precede it. The opening `<?php`, whitespace, and comments are not statements; output, variable assignments, and `require` cannot come first. If a namespaced file also needs global code, it must use a bracketed `namespace { ... }` block.

Backslashes separate levels in a namespace name. The name does not inherently have to match a directory, but an autoloading standard can add that correspondence. Use stable organizational boundaries such as `App\Billing`; do not mechanically turn every folder into another layer of business taxonomy.

### Four name forms

PHP resolves statically written class-like names when compiling a file. Class-like names include classes, interfaces, traits, and enums; functions and constants use similar but separate import tables.

| Form | Example | Resolution |
| --- | --- | --- |
| Unqualified name | `Invoice` | Use the corresponding import alias first; a class-like name otherwise belongs to the current namespace |
| Qualified name | `Model\Invoice` | Replace the first segment if it is an import alias; otherwise append the name to the current namespace |
| Fully qualified name | `\Vendor\Billing\Invoice` | Start at the global root and ignore the current namespace |
| `namespace`-relative name | `namespace\Invoice` | Explicitly append the name to the current namespace |

A fully qualified name starts with a backslash and is unambiguous, but repeating long names everywhere can obscure the business code. Usually you import once at the top and use a short name in the body. Diagnostics, configuration, and dynamic construction often need the complete name as a string without the leading backslash.

### Import tables and aliases

`use Vendor\Billing\Invoice;` establishes the alias `Invoice` for class-like names in the current file. `use Vendor\Billing\Invoice as VendorInvoice;` establishes an explicit namespace alias. An alias resolves two imports with the same short name, and it can also name a namespace prefix, as in `use Vendor\Billing as Billing;`.

Functions and constants do not borrow the class-like import table. They use `use function Vendor\Text\slugify;` and `use const Vendor\Config\VERSION;`, respectively. Group imports can share a prefix, but mixed groups still need the `function` and `const` keywords, so several ordinary imports are often easier to read than a long mixed group.

An import takes effect at compile time and applies only to its file. An included file does not inherit the caller's imports, and the caller does not receive imports from the included file. Keeping `use` declarations directly after `namespace` and before other declarations makes that file-level convention visible.

### Global fallback for functions and constants

An unimported, unqualified class-like name resolves only in the current namespace; it does not automatically try a global class. Inside `App\Billing`, `new DateTimeImmutable()` means `App\Billing\DateTimeImmutable`. Import the built-in class or write `new \DateTimeImmutable()` to use it.

Unqualified functions and constants behave differently. PHP first tries the same name in the current namespace and then falls back to the global name if it finds none. Consequently, a local function may shadow `strlen()`, while `\strlen()` always selects the global function. A leading backslash makes the target stable if calling that built-in is part of the contract.

Qualified function names, qualified constant names, and fully qualified names do not perform this fallback. An import also determines the target directly. During review, distinguish “what this file imported” from “what PHP tried globally after an unqualified function was missing”; the steps happen at different phases.

### Static names and dynamic strings

`Invoice::class` applies the current file's imports to produce a complete class-name string, and obtaining that string alone does not load the class. It is useful in dependency-injection configuration, factory maps, and serialization metadata because refactoring tools can recognize it more reliably.

A dynamic string is not sent back through import resolution. `new $class`, `class_exists($class)`, and string entries in callback arrays use the name contained in the string; a value of `'Invoice'` means the global short name, not an imported `App\Models\Invoice`. Pass `Invoice::class` from the caller or keep class constants in a trusted map instead of assembling names manually.

### Included files retain their own name context

A file targeted by `include` or `require` compiles the namespace declarations and imports in its own source. A caller in `App\Web` does not automatically place an included file with no namespace declaration into `App\Web`. Classes declared by that file remain in global scope.

An included file can access the variable scope at the inclusion site, but that variable rule does not change declaration names. Treating variable scope and namespaces as the same boundary creates accidental dependencies that no `use` list reveals.

Including a file that declares a class or function twice can still fail with a duplicate declaration; a namespace does not turn the second load into a separate copy. Use `require_once` when a declaration file must be loaded exactly once, although application classes should usually be loaded on demand by an autoloader.

PSR-4 cannot discover free functions one at a time. An entry point can load a function library explicitly, or Composer can load it through `autoload.files`; either way, its namespace declaration and function imports remain independently resolved per file.

## Examples

The local PHP 8.3.33 CLI produced the output for all three examples. They build from colliding class names to global fallback and dynamic class names.

### Distinguishing same-named classes with aliases

Two packages can both declare `Receipt`. The global code imports one under its original name and one under an alias, keeping each short name unique in this file.

<!-- quick -->

```php
// file: receipt_aliases.php
<?php

declare(strict_types=1);

namespace Commerce\Sales {
    final class Receipt
    {
        public function label(): string
        {
            return 'sales receipt';
        }
    }
}

namespace Commerce\Returns {
    final class Receipt
    {
        public function label(): string
        {
            return 'return receipt';
        }
    }
}

namespace {
    use Commerce\Returns\Receipt as ReturnReceipt;
    use Commerce\Sales\Receipt;

    $issued = new Receipt();
    $returned = new ReturnReceipt();

    printf("%s: %s\n", $issued::class, $issued->label());
    printf("%s: %s\n", $returned::class, $returned->label());
}
```

```text
Commerce\Sales\Receipt: sales receipt
Commerce\Returns\Receipt: return receipt
```

<!-- /quick -->

`Receipt` and `ReturnReceipt` are only local spellings in the last namespace block. The objects still belong to the complete names used at declaration. An alias neither creates a new class nor changes the name returned by reflection, logs, or `$object::class`.

The bracketed syntax lets this single-file example declare two namespaces and execute global code. A real PSR-4 project would normally put the classes in separate files and let its entry point load Composer's autoloader.

### Observing function fallback

`App\Text` declares its own `strlen()`, so an unqualified call selects it. PHP falls back to the global function only because no local `count()` exists; the leading backslash skips local candidates.

```php
// file: function_fallback.php
<?php

declare(strict_types=1);

namespace App\Text {
    function strlen(string $value): int
    {
        return \strlen($value) * 10;
    }

    const MODE = 'namespaced';

    function report(): void
    {
        printf("local strlen: %d\n", strlen('php'));
        printf("global strlen: %d\n", \strlen('php'));
        printf("global count: %d\n", count(['a', 'b', 'c']));
        printf("mode: %s\n", MODE);
    }
}

namespace {
    \App\Text\report();
}
```

```text
local strlen: 30
global strlen: 3
global count: 3
mode: namespaced
```

The local `strlen()` must call `\strlen()` internally; otherwise it recurses into itself. The unprefixed `count()` works only because there is no `App\Text\count()`. If the global target is part of the contract, spelling it explicitly is more robust.

The constant `MODE` also belongs to `App\Text`. If PHP finds neither that constant nor a global constant with the same name, it raises an undefined-constant error instead of silently treating the name as a string.

### Passing complete names to dynamic construction

An import affects the static expression `Invoice::class`, but it does not rewrite the ordinary string `'Invoice'`. The factory accepts an already resolved complete name, so it does not have to guess the caller's namespace context.

```php
// file: dynamic_class_names.php
<?php

declare(strict_types=1);

namespace App\Models {
    final class Invoice {}
}

namespace App\Factories {
    use App\Models\Invoice;

    final class Factory
    {
        public static function make(string $class): object
        {
            return new $class();
        }

        public static function localName(): string
        {
            return namespace\Factory::class;
        }
    }

    function candidateNames(): array
    {
        return ['Invoice', Invoice::class, \App\Models\Invoice::class];
    }
}

namespace {
    use App\Factories\Factory;
    use function App\Factories\candidateNames;

    foreach (candidateNames() as $candidate) {
        printf("%s => %s\n", $candidate, class_exists($candidate) ? 'found' : 'missing');
    }

    echo Factory::make(\App\Models\Invoice::class)::class, "\n";
    echo Factory::localName(), "\n";
}
```

```text
Invoice => missing
App\Models\Invoice => found
App\Models\Invoice => found
App\Models\Invoice
App\Factories\Factory
```

The last two values in the array are identical: imported `Invoice::class` and the fully qualified source name both produce `App\Models\Invoice`. The leading backslash marks the root in source code; it is not retained in the resulting class-name string.

`namespace\Factory::class` explicitly refers to `Factory` in the current namespace. It is occasionally useful in fixtures that move a whole namespace. Public configuration usually reads more clearly with an imported `Factory::class` that readers can trace directly.

## Pitfalls

### Mistaking an import for a load

> **Pitfall:** `use Vendor\Package\Client;` does not execute `require` or guarantee that `Client` is defined. It only tells the compiler what a short name means in this file.

When code first uses a class-like name, PHP may pass the resolved complete name to registered autoloaders. If `vendor/autoload.php` was not loaded, Composer's map is stale, or the path does not match, changing the `use` declaration cosmetically still ends in `Class ... not found`.

**Fix:** load the autoloader at the application entry point, run `composer dump-autoload` after changing Composer autoload configuration, and exercise the resolved name through `class_exists(ExpectedType::class)` or the real construction path. Log the exact complete name received by the autoloader before inspecting its mapping.

### Reusing one short name in two imports

> **Pitfall:** The same import table cannot give both `Vendor\One\Logger` and `Vendor\Two\Logger` the alias `Logger`. The second import causes a compile-time name conflict.

Removing one import and scattering fully qualified names through the body may run, but it spreads the conflict policy across call sites. Reducing both types to cryptic initials merely exchanges the collision for a readability defect.

**Fix:** keep the natural short name for one domain type and give the other a role- or source-bearing alias such as `VendorLogger` or `AuditLogger`. Aliases are local to a file, so make a clear choice independently in each file.

### Assuming dynamic strings use imports

> **Pitfall:** Generated code often puts `$class = 'Invoice'; new $class();` in a file that imports `App\Models\Invoice`. The string does not become a complete name.

Manually joining `__NAMESPACE__ . '\\' . $suffix` fits only a closed protocol in which every target genuinely belongs to the current namespace. If `$suffix` comes from a request, this also turns external input into a type-selection mechanism and may autoload and construct an unintended class with side effects.

**Fix:** for a closed set, use an explicit allowlist from external keys to class constants such as `Invoice::class`. Validate the shared interface after construction and reject unknown keys before construction; never use `eval` or treat user input directly as a class name.

### Forgetting that classes do not fall back globally

> **Pitfall:** Inside `App\Jobs`, `new DateTimeImmutable()` resolves to `App\Jobs\DateTimeImmutable`. Function fallback to global scope does not imply class fallback.

This often appears after a global script is moved into a namespace. Its `strlen()` calls keep working, while built-in classes suddenly report as missing, making an extension or Composer problem look more likely than a resolution error.

**Fix:** import global classes you use or spell the complete name `\DateTimeImmutable` at a small number of call sites. When moving a file, inventory class-like names, functions, and constants separately rather than inferring one category's behavior from another.

### Letting PSR-4 case or hierarchy drift

> **Pitfall:** With `Acme\` mapped to `src/`, `Acme\Billing\InvoiceWriter` belongs at `src/Billing/InvoiceWriter.php`, with directory, file, and class-name case kept consistent.

A case-insensitive development filesystem can hide the difference between `billing` and `Billing` until deployment to a case-sensitive system. Another common drift puts a file under `Services` while its declaration says `Service`, sending the autoloader to a different path.

**Fix:** update namespaces and references in the same change that moves a file, then run the real entry point or tests in case-sensitive CI. Verify expected classes through Composer's autoloader rather than running only `php -l` on individual files.

### Confusing imports with closure capture

> **Pitfall:** File-level `use App\Models\Invoice;` imports a class name, while `function () use ($invoice) { ... }` copies a variable into a closure or captures it by reference. The keyword is the same, but the operations are unrelated.

Generated code sometimes puts a class import inside a method or tries to import a variable with a file-level `use`. Both fail: a namespace import is a top-level declaration, while closure `use (...)` can only follow an anonymous function's parameter list.

**Fix:** call the operations `namespace import` and `closure capture` during review. Put class names in the import area after the namespace declaration, put runtime variables in the closure capture list, and separately inspect lifetime and mutability for by-reference captures.

<!-- deep -->

## The compile-time boundary of name resolution

PHP processes namespace declarations, imports, and static class-like references while compiling a file. After `use App\Models\Invoice as Bill;` establishes an alias, `Bill::class`, `new Bill()`, and the type declaration `Bill $invoice` all refer to the same complete name. An alias is neither a second class in the runtime table nor `class_alias()`; the latter establishes another class name visible to runtime lookup and has substantially stronger semantics.

Import tables are separate by symbol kind. A file may simultaneously import class `Vendor\Clock`, function `Vendor\Clock()`, and constant `Vendor\Clock`; their short names are identical but do not overwrite one another. That code is legal but hard to read, so choose names that communicate kind and role unless a library API forces the collision.

The result of `::class` is a string, and obtaining it does not trigger autoloading. Passing that string to `new`, to the default autoloading path of `class_exists()`, or to a reflection operation that needs the type may then call an autoloader. This phase boundary explains why container configuration can safely collect names of unloaded classes, and why merely printing `Type::class` cannot prove the mapping works.

`__NAMESPACE__` similarly produces the current namespace string. It fits diagnostics and limited metaprogramming, but concatenated strings do not receive the same support from static analysis and rename tools as `::class`. Prefer an explicit map of class constants when the target set is known.

### A namespace is not an access-control boundary

A namespace grants no special access to code in the same namespace. `public`, `protected`, and `private` still follow class and inheritance relationships, not name prefixes. PHP has no namespace-level “package private” modifier.

Any code can refer to an accessible public declaration by its complete name without writing `use`. An import only changes local spelling; it neither grants permission nor hides a type. Do not treat a hard-to-guess namespace name as a security control.

A namespace also does not install or version a package. Composer package dependencies, autoload configuration, and release boundaries do that work. Two packages can still incorrectly declare the same complete class name and collide when loaded.

If an architecture requires modules to call one another only through public interfaces, enforce it with directory ownership, Composer package boundaries, static-analysis rules, and tests. Namespaces can express the design, but they do not prevent a cross-boundary reference by themselves.

## The PSR-4 and Composer boundary

PSR-4 defines an autoloading convention from a complete class-like name to a file path. If the prefix `Acme\` maps to `src/`, an autoloader removes that prefix, turns the remaining namespace separators into directory separators, and adds `.php` to the terminating class name. `Acme\Billing\InvoiceWriter`, for example, maps to `src/Billing/InvoiceWriter.php`.

Composer records the mapping under `autoload.psr-4` in `composer.json`. Test-only names belong under the root package's `autoload-dev.psr-4`, so they are not exposed to projects that consume the package. Run `composer dump-autoload` after changing the configuration, and load `vendor/autoload.php` once from the entry point.

```json
{
  "autoload": {
    "psr-4": {
      "Acme\\": "src/"
    },
    "files": ["src/functions.php"]
  },
  "autoload-dev": {
    "psr-4": {
      "Acme\\Tests\\": "tests/"
    }
  }
}
```

PSR-4 handles class-like declarations such as classes, interfaces, traits, and enums; it does not lazily autoload free functions or constants. Composer's `autoload.files` can include a function file when initializing the autoloader, but that loading model differs from one-file-per-class PSR-4 lookup. A static utility class is another option for a large function API, but the API shape, not an accommodation for the loader, should drive that modeling choice.

One prefix can map to several base directories, which Composer searches in configuration order. Overlapping prefixes can also exist, with the more specific prefix expressing narrower ownership. Do not rely on whichever duplicate definition happens to be found first; two definitions for one complete class name make deployment depend on installed contents and loading order.

The only connection between `use` and PSR-4 is the resolved complete name. The compiler first resolves `InvoiceWriter` to `Acme\Billing\InvoiceWriter`; an autoloader then tries to find the file. Debug in that order and record two facts: the exact name PHP requested and the exact path implied by the prefix mapping.

<!-- /deep -->

[Checkpoint: php/namespaces](https://codewiki.com/php/namespaces/#checkpoint)

## Further reading

- [PHP manual: namespaces overview](https://www.php.net/manual/en/language.namespaces.php)
- [PHP manual: name resolution rules](https://www.php.net/manual/en/language.namespaces.rules.php)
- [PHP manual: importing and aliasing](https://www.php.net/manual/en/language.namespaces.importing.php)
- [PHP manual: fallback to global scope](https://www.php.net/manual/en/language.namespaces.fallback.php)
- [PHP-FIG: PSR-4 autoloader specification](https://www.php-fig.org/psr/psr-4/)
- [Composer schema: PSR-4](https://getcomposer.org/doc/04-schema.md#psr-4)
