objects namespaces and application architecture

Namespaces

Namespaces give PHP classes, interfaces, traits, enums, functions, and constants a qualified name. They prevent name collisions and make ownership clear in larger projects.

Without namespaces, every class lives in the global namespace. Two packages cannot both define a class called User, Request, or Collection without clashing. Namespaces solve that by giving each class a path-like prefix.

Declaring A Namespace

A namespace declaration usually appears after declare(strict_types=1); and before the class.

PHP example
<?php

declare(strict_types=1);

namespace App\Billing\Domain;

final readonly class InvoiceId
{
    public function __construct(
        public int $value,
    ) {
        if ($value <= 0) {
            throw new \InvalidArgumentException('Invoice ID must be positive.');
        }
    }
}

The full class name is App\Billing\Domain\InvoiceId. Inside the namespace, the short name InvoiceId is enough.

Importing Classes With use

The use statement imports a class name so you do not need to write its full name everywhere.

PHP example
<?php

declare(strict_types=1);

namespace App\Billing\Application;

use App\Billing\Domain\InvoiceId;

final class MarkInvoicePaid
{
    public function handle(int $invoiceId): InvoiceId
    {
        return new InvoiceId($invoiceId);
    }
}

use does not load the file by itself. It only tells PHP which fully qualified class name the short name refers to. Loading is handled by includes or an autoloader.

Fully Qualified Names

A fully qualified name starts from the global namespace. In code, that often means a leading backslash.

PHP example
<?php

declare(strict_types=1);

namespace App\Support;

final class Clock
{
    public function today(): \DateTimeImmutable
    {
        return new \DateTimeImmutable('today');
    }
}

DateTimeImmutable is a built-in global class. The leading \ makes it clear that PHP should look for \DateTimeImmutable, not App\Support\DateTimeImmutable.

Many teams import global classes instead:

PHP example
<?php

declare(strict_types=1);

namespace App\Support;

use DateTimeImmutable;

final class Clock
{
    public function today(): DateTimeImmutable
    {
        return new DateTimeImmutable('today');
    }
}

Both styles are valid. Follow the project's existing convention.

Aliases

Aliases help when two classes have the same short name.

PHP example
<?php

declare(strict_types=1);

namespace App\Reporting;

use App\Billing\Domain\Invoice as BillingInvoice;
use App\Sales\Domain\Invoice as SalesInvoice;

final class InvoiceComparison
{
    public function describe(BillingInvoice $billingInvoice, SalesInvoice $salesInvoice): string
    {
        return 'Comparing billing invoice with sales invoice.';
    }
}

The alias should clarify meaning. If aliases become frequent, the class names or boundaries may need review.

Namespaces And Functions

Functions can also live in namespaces.

PHP example
<?php

declare(strict_types=1);

namespace App\Support;

function normalise_email(string $email): string
{
    return strtolower(trim($email));
}

echo normalise_email(' Ada@Example.COM ') . PHP_EOL;

// Prints:
// ada@example.com

Namespaced functions can be useful, but many PHP projects prefer static methods or small services for application code. Again, follow the local style.

Namespaces And Project Structure

Modern PHP projects usually pair namespaces with an autoloading rule. The common convention is that namespace segments match folders.

App\Billing\Domain\InvoiceId
src/Billing/Domain/InvoiceId.php

That convention makes classes easy to find. When a class is named App\Courses\Application\PublishCourse, another developer expects to find it near src/Courses/Application/PublishCourse.php.

The next lesson covers autoloading in more detail. For this lesson, focus on the naming rule: namespaces describe where a class belongs in the codebase.

Common Mistakes

One common mistake is forgetting that use statements do not include files. If there is no autoloader or include, PHP still cannot find the class.

Another mistake is using the wrong namespace after moving a file. The namespace should match the intended class name, not only the folder it happens to be in.

It is also easy to forget global classes in namespaced code. new Exception() inside App\Service means PHP first looks for App\Service\Exception. Use \Exception or use Exception.

What You Should Be Able To Do

After this lesson, you should be able to declare a namespace, import classes with use, recognise fully qualified class names, use aliases when short names collide, and understand how namespaces communicate code ownership.

For junior work, this matters because most modern PHP projects rely on namespaces everywhere. Reading a namespace tells you where a class belongs and often what responsibility it should have.

Practice

Practice: Use Namespaces And Aliases

Create a small PHP example with two classes that have the same short name in different namespaces.

Task

Build:

  • a billing Invoice class
  • a sales Invoice class
  • a reporting class that imports both using aliases
  • a method that accepts both invoice types

Use strict types. Keep the expected output in the PHP code block as printed lines or comments.

Check Your Work

Confirm:

  • each class has the correct namespace
  • use ... as ... prevents the short-name collision
  • the reporting class can refer to both invoice types clearly

Afterward, explain why namespaces are better than trying to make every class name globally unique.

Show solution

This solution uses bracketed namespaces so the example can run as one file.

PHP example
<?php

declare(strict_types=1);

namespace App\Billing\Domain {
    final readonly class Invoice
    {
        public function __construct(
            public int $id,
        ) {
        }
    }
}

namespace App\Sales\Domain {
    final readonly class Invoice
    {
        public function __construct(
            public string $reference,
        ) {
        }
    }
}

namespace App\Reporting {
    use App\Billing\Domain\Invoice as BillingInvoice;
    use App\Sales\Domain\Invoice as SalesInvoice;

    final class InvoiceReport
    {
        public function describe(BillingInvoice $billing, SalesInvoice $sales): string
        {
            return 'Billing #' . $billing->id . ' matches sales ' . $sales->reference . '.';
        }
    }
}

namespace {
    use App\Billing\Domain\Invoice as BillingInvoice;
    use App\Reporting\InvoiceReport;
    use App\Sales\Domain\Invoice as SalesInvoice;

    $report = new InvoiceReport();

    echo $report->describe(
        new BillingInvoice(10),
        new SalesInvoice('S-2026-0001'),
    ) . PHP_EOL;

    // Prints:
    // Billing #10 matches sales S-2026-0001.
}

Namespaces let both modules use the meaningful short name Invoice without colliding. Aliases make the reporting code explicit when it needs to work with both concepts at the same time.