Namespaces: Organizing Code and Resolving Names

Understand how PHP namespaces organize code and prevent name collisions, how qualified vs unqualified vs fully-qualified names resolve, how use imports (including aliases and grouped imports) work for classes, functions, and constants, and how the global namespace interacts with all of it.

Before namespaces existed (PHP 5.3, 2009), every class, function, and constant in a PHP codebase lived in one single flat global bucket — which meant two libraries both defining a class named Request or a function named validate() simply could not coexist in the same project without one of them renaming things by hand, usually with long prefixes like Zend_Http_Request. Namespaces solve this by giving every symbol a fully qualified location, the same way a filesystem path (/var/www/app.php) disambiguates two files that happen to share the name app.php in different directories. Composer's entire autoloading model, and by extension nearly all modern PHP package interoperability, is built directly on top of namespaces.

Declaring a namespace and what it actually does

A file declares its namespace with namespace App\Domain\Orders; as the very first statement (only a leading declare(strict_types=1); or a comment may precede it). Everything declared in that file — classes, interfaces, traits, enums, functions, and constants — is now scoped under that namespace, so the class Order in that file is really App\Domain\Orders\Order, distinct from any other Order class declared under a different namespace anywhere else in the project or in a third-party library.

A namespace is purely an organizational and name-resolution construct — it has no effect on visibility, access control, or runtime behavior beyond how a name is looked up. It does not need to match a directory structure at the language level (PHP itself doesn't care), but PSR-4 autoloading (covered in depth in the next lesson) conventionally maps namespace segments to directory segments, which is why in practice a well-organized PHP project's namespace hierarchy usually does mirror its folder layout.

One PHP file may declare at most one namespace using the simple namespace Foo; syntax (technically multiple namespaces per file are allowed only using a braced block syntax, namespace Foo { ... }, but this is rarely used in real code and actively discouraged — one namespace per file is the near-universal convention).

Unqualified, qualified, and fully qualified names

PHP resolves a class/function/constant reference differently depending on its exact written form. An unqualified name like Order (no backslash anywhere) is resolved relative to the current namespace first — inside namespace App\Domain\Orders;, writing new Order() looks for App\Domain\Orders\Order. A qualified name like Orders\Order (contains a backslash but doesn't start with one) is also resolved relative to the current namespace, appending the current namespace as a prefix. A fully qualified name like \App\Domain\Orders\Order (starts with a leading backslash) is resolved from the global namespace root, completely independent of whatever namespace the current file is in — it always means exactly one thing, everywhere.

This distinction matters most for unqualified function and constant calls, which behave differently from unqualified class names: an unqualified class reference (new Order()) is resolved against the current namespace and, critically, does NOT fall back to the global namespace if not found there — it simply fails. An unqualified function or constant call (strlen($x), PHP_EOL), by contrast, DOES fall back to the global namespace at runtime if no function/constant by that name exists in the current namespace — which is precisely why namespaced code can still call PHP's thousands of built-in functions without qualifying every single one.

The global namespace itself is written as a leading backslash with nothing before the next segment, \ClassName, and every PHP built-in class (\Exception, \DateTime, \ArrayObject) and built-in function/constant lives there. Any code with no namespace declaration at all is implicitly running in the global namespace.

use imports: classes, aliasing, and grouped imports

A use statement imports a fully qualified name into the current file's scope, letting you refer to it with a shorter, unqualified name for the rest of the file: use App\Domain\Orders\Order; then lets you write plain Order anywhere below it, instead of the full App\Domain\Orders\Order every time. use statements must appear near the top of the file, after the namespace declaration and before any other code (though multiple use statements can be stacked, one per line, in any order relative to each other).

When two imported classes would collide under the same short name — say, both a domain App\Domain\Orders\Status and a third-party Vendor\Payment\Status are needed in the same file — an alias resolves the conflict: use App\Domain\Orders\Status; use Vendor\Payment\Status as PaymentStatus; lets the file refer to Status (the domain one) and PaymentStatus (the vendor one) without ambiguity.

PHP 7+ supports grouped imports for pulling multiple symbols from the same namespace prefix in one statement: use App\Domain\Orders\{Order, OrderId, OrderStatus}; is exactly equivalent to three separate use lines, and is purely a readability convenience for files that import many symbols from the same location. Grouped imports can mix aliasing per item: use App\Domain\Orders\{Order, OrderStatus as Status};.

Importing functions and constants, not just classes

use isn't limited to classes — PHP also supports use function and use const for importing namespaced functions and constants specifically, which is necessary because a namespace can contain a function and a class with the same short name simultaneously (App\Domain\Orders\format() the function and, separately, some class also happening to be named format would not collide, since they occupy different symbol tables), and a plain use App\Domain\Orders\format; only ever imports the class/interface/trait meaning, never the function.

use function App\Domain\Orders\formatOrderNumber; imports a namespaced function so it can be called unqualified as formatOrderNumber($id), and use const App\Domain\Orders\DEFAULT_CURRENCY; does the same for a namespaced constant, letting it be referenced as plain DEFAULT_CURRENCY. These are used far less often than use for classes in practice, simply because most PHP code organizes reusable logic into static methods on classes rather than free namespaced functions — but libraries built around small utility function sets (functional-style helper libraries) rely on them heavily.

Grouped imports support the function/const forms too, though each group can only mix one kind consistently per PHP's grammar in the compact single-keyword form — in practice, most codebases keep function and constant imports on their own separate use function / use const lines rather than combining them with class imports in one group, for clarity.

Namespace declaration and use import forms

A leading backslash always means 'start resolution from the global namespace root', regardless of where the code currently is. Unqualified class names never fall back to the global namespace if not found in the current one; unqualified function/constant calls do fall back automatically. use, use function, and use const are three separate import mechanisms because classes/interfaces/traits, functions, and constants each have their own symbol table per namespace.

<?php
declare(strict_types=1);

namespace App\Domain\Orders;

// Plain class import
use App\Infrastructure\Persistence\DoctrineOrderRepository;

// Aliased import (avoids a name collision)
use Vendor\Payment\Status as PaymentStatus;

// Grouped import: several symbols, same namespace prefix
use App\Domain\Shared\{Money, EmailAddress, DateRange};

// Grouped import with a per-item alias
use App\Domain\Shipping\{Address, Status as ShippingStatus};

// Importing a namespaced function and a namespaced constant
use function App\Domain\Orders\formatOrderNumber;
use const App\Domain\Orders\DEFAULT_CURRENCY;

// Name resolution forms, inside this namespace:
// Order                       -> App\Domain\Orders\Order        (unqualified)
// Shipping\Address            -> App\Domain\Orders\Shipping\Address (qualified)
// \App\Domain\Orders\Order    -> App\Domain\Orders\Order        (fully qualified, from root)
// \DateTime                   -> global namespace's DateTime (built-in)
// strlen($x)                  -> falls back to global \strlen() automatically

Code Examples

Two entirely unrelated classes are both legitimately named Order, and they coexist without conflict because each lives in its own namespace. Referring to each with a fully qualified name (leading backslash) resolves unambiguously to the correct one regardless of the current file's own namespace. Expected result: App\Domain\Orders\Order\nApp\Domain\Shipping\Order

This controller imports a class group, aliases a colliding vendor class to PaymentStatus, and imports a plain namespaced function — three different use forms working together. \RuntimeException is referenced with a leading backslash because it lives in the global namespace, not App\Http\Controllers.

use Vendor\Payment\Status as PaymentStatus;

formatOrderNumber (a function) and OrderNumber (a class) coexist under the same namespace without conflict because PHP tracks functions and classes in separate symbol tables. The consuming file needs three distinct import forms — use function, use const, and plain use — to pull in the function, the constant, and the class respectively. Expected result: ORD-42 USD ORD-42

<?php
// File: src/Domain/Orders/Order.php
namespace App\Domain\Orders;

class Order
{
    public function __construct(public readonly string $id) {}
}

// File: src/Domain/Shipping/Order.php  -- a DIFFERENT class, same short name
namespace App\Domain\Shipping;

class Order
{
    public function __construct(public readonly string $trackingCode) {}
}

// File: src/report.php
namespace App;

$order1 = new \App\Domain\Orders\Order('ORD-1');
$order2 = new \App\Domain\Shipping\Order('TRACK-9');

echo get_class($order1) . "\n";
echo get_class($order2) . "\n";
<?php
namespace App\Http\Controllers;

use App\Domain\Orders\{Order, OrderId};
use App\Domain\Orders\OrderRepository;
use Vendor\Payment\Status as PaymentStatus;
use function App\Domain\Orders\formatOrderNumber;

class OrderController
{
    public function __construct(private OrderRepository $orders) {}

    public function show(string $rawId): string
    {
        $order = $this->orders->findById(OrderId::fromString($rawId));

        if ($order === null) {
            throw new \RuntimeException('Order not found'); // global namespace built-in
        }

        $status = PaymentStatus::Paid; // the vendor Status, aliased to avoid clashing

        return formatOrderNumber($order->id) . ' — ' . $status->name;
    }
}
<?php
namespace App\Domain\Orders;

const DEFAULT_CURRENCY = 'USD';

function formatOrderNumber(string $id): string
{
    return strtoupper("ORD-{$id}");
}

class OrderNumber
{
    // Deliberately shares its short name pattern with the function above,
    // but occupies a different symbol table (class vs function), so no collision.
    public static function format(string $id): string
    {
        return formatOrderNumber($id); // unqualified call resolves to THIS namespace's function first
    }
}

// --- consuming file ---
namespace App\Reporting;

use function App\Domain\Orders\formatOrderNumber;
use const App\Domain\Orders\DEFAULT_CURRENCY;
use App\Domain\Orders\OrderNumber;

echo formatOrderNumber('42') . "\n";      // via use function import
echo DEFAULT_CURRENCY . "\n";             // via use const import
echo OrderNumber::format('42') . "\n";    // via ordinary class use

Name resolution decision path

Fully qualified names (leading backslash) always resolve from the global root. Unqualified class names never fall back to global; unqualified function/constant calls do.

Reference written in code
         |
   starts with \ ?
     /        \
   yes         no
    |            |
  resolve      contains \ ?
  from root      /     \
  (fully        yes     no
  qualified)     |       |
              qualified  unqualified
              (prefix    |
              current    is it a class/interface/trait?
              namespace)   /              \
                          yes              no (function/const)
                           |                 |
                    look in current    look in current namespace,
                    namespace ONLY -   if not found FALL BACK
                    no fallback        to the global namespace

Important Rules

namespace must be the first statement in a file (only declare() or a comment may precede it), and one namespace per file is the standard convention.

A leading backslash (\App\Foo) always resolves from the global namespace root, independent of the current file's own namespace.

Unqualified class/interface/trait names resolve only against the current namespace — there is no automatic fallback to the global namespace.

Unqualified function and constant calls DO fall back to the global namespace automatically if not found in the current namespace, which is why namespaced code can call built-ins like strlen() unqualified.

use imports a class/interface/trait; use function and use const are separate mechanisms required for importing namespaced functions and constants respectively.

Grouped imports (use App\Ns\{A, B, C};) are purely a readability shorthand for multiple use statements sharing a namespace prefix — they change nothing about resolution semantics.

Where this shows up

Every Composer package in the PHP ecosystem — Symfony components, Laravel's framework classes, PHPUnit, Monolog — ships its own top-level vendor namespace (Symfony\Component\HttpFoundation\Request, PHPUnit\Framework\TestCase) specifically so that installing dozens of independent packages in one project never causes a class name collision. Large applications organize their own code the same way, typically under a project-specific root namespace like App\ or a company name, with subnamespaces mirroring architectural layers (App\Domain, App\Infrastructure, App\Http\Controllers), which is also exactly what PSR-4 autoloading, covered next, depends on to locate files automatically.

namespace App\Domain\Orders; function process(): void { $date = new DateTime(); // fails to resolve as expected } namespace App\Domain\Orders; function process(): void { $date = new \DateTime(); // fully qualified: resolves from the global namespace }

Unqualified class names never fall back to the global namespace. Inside App\Domain\Orders, an unqualified new DateTime() looks specifically for App\Domain\Orders\DateTime, fails to find it, and throws a fatal 'class not found' error — even though PHP's built-in \DateTime clearly exists. A leading backslash is required to reach it.

use App\Domain\Orders\formatOrderNumber; // imports the CLASS meaning only echo formatOrderNumber('42'); // fails: no class-level symbol was called as a function correctly here use function App\Domain\Orders\formatOrderNumber; // imports the FUNCTION specifically echo formatOrderNumber('42');

A plain use statement only ever imports the class/interface/trait meaning of a name, never the function meaning, even when the target namespace only defines a function with that name. Importing a namespaced function requires the explicit use function form.

[multiple-choice] Inside namespace App\Domain\Orders;, what does the unqualified reference new DateTime() do? A. It correctly finds PHP's built-in global \DateTime class automatically B. It looks only for App\Domain\Orders\DateTime and fails with a class-not-found error, since unqualified class names never fall back to the global namespace C. It causes a syntax error at compile time D. It is resolved the same as \DateTime because class names always fall back to global

B. It looks only for App\Domain\Orders\DateTime and fails with a class-not-found error, since unqualified class names never fall back to the global namespace — Unlike unqualified function/constant calls, unqualified class names resolve strictly against the current namespace with no fallback. Reaching PHP's built-in DateTime from inside a namespace requires the fully qualified \DateTime.

[multiple-choice] Which statement correctly imports a namespaced function (not a class) so it can be called unqualified? A. use App\Domain\Orders\formatOrderNumber; B. use function App\Domain\Orders\formatOrderNumber; C. import App\Domain\Orders\formatOrderNumber; D. use App\Domain\Orders\{function formatOrderNumber};

B. use function App\Domain\Orders\formatOrderNumber; — A plain use statement only ever imports the class/interface/trait meaning of a name. Importing a namespaced function specifically requires the use function form, since functions and classes occupy separate symbol tables per namespace.

[predict-output] Given use App\Domain\Inventory\Status; use App\Domain\Shipping\Status as ShippingStatus;, which short name refers to App\Domain\Shipping\Status in the rest of the file? A. Status B. ShippingStatus C. Shipping\Status D. Both Status and ShippingStatus refer to it

B. ShippingStatus — The alias (as ShippingStatus) is what the file must use to refer to App\Domain\Shipping\Status, since the unqualified name Status was already claimed by the first, unaliased import (App\Domain\Inventory\Status).

Given two classes that both need to be used in the same file — App\Domain\Inventory\Status and App\Domain\Shipping\Status — write the use import statements needed to reference both without a collision, using an alias for one of them. Then write one grouped import statement that imports Product, ProductId, and Sku all from the App\Domain\Catalog namespace in a single line.

use App\Domain\Inventory\Status; use App\Domain\Shipping\Status as ShippingStatus; use App\Domain\Catalog\{Product, ProductId, Sku};

Only one of the two Status classes can keep the unqualified name Status; the other needs as SomeAlias. Grouped imports use the use Namespace\{A, B, C}; syntax.