Namespaces and Autoloading

Real applications have hundreds of classes, plus thousands more from libraries. Namespaces give classes unique, organised names — App\Models\User rather than just User — so they never clash. Autoloading loads each class's file automatically the first time it's used, so you never write long lists of require statements.

Declaring a namespace

A namespace declaration at the top of a file puts everything in that file into the namespace. The class's full name then includes it:

PHP
<?php

namespace App\Models;

class User
{
    public function __construct(public string $name) {}
}

$u = new User("Ana");
echo get_class($u), "\n";
echo User::class, "\n";
echo __NAMESPACE__, "\n";
Output
App\Models\User
App\Models\User
App\Models

Namespaces use backslashes as separators. By convention they're PascalCase, start with a vendor or project name (App, Acme), and mirror the folder structure.

use: importing names

From another namespace, refer to a class by its full name, or import it with use and then use the short name. as gives an alias, which resolves clashes between classes with the same short name:

PHP src/Models/User.php
<?php

namespace App\Models;

class User
{
    public function __construct(public string $name) {}
}
PHP src/Http/User.php
<?php

namespace App\Http;

class User
{
    public function __construct(public string $ip) {}
}
PHP main.php
<?php

namespace App;

require __DIR__ . '/src/Models/User.php';
require __DIR__ . '/src/Http/User.php';

use App\Models\User;
use App\Http\User as HttpUser;

$account = new User("Ana");
$visitor = new HttpUser("203.0.113.7");
$fullName = new \App\Models\User("Ben");    // leading \ = fully qualified

echo $account->name, " ", $visitor->ip, " ", $fullName->name, "\n";
echo get_class($visitor), "\n";
Output
Ana 203.0.113.7 Ben
App\Http\User

Global functions and classes

Built-in classes live in the global namespace. Inside a namespace, an unqualified class name is looked up in the current namespace, so built-in classes need a leading backslash or a use statement. Functions and constants fall back to the global ones automatically:

PHP
<?php

namespace App\Services;

use DateTimeImmutable;

$date = new DateTimeImmutable('2026-01-01');   // via use
$obj = new \ArrayObject([1, 2, 3]);             // via leading backslash
echo $date->format('Y'), " ", count($obj), " ", strtoupper("functions just work"), "\n";

$broken = new Exception("oops");
Output
2026 3 FUNCTIONS JUST WORK

Fatal error: Uncaught Error: Class "App\Services\Exception" not found in main.php:11
Stack trace:
#0 {main}
  thrown in main.php on line 11

That last line is one of the most common namespace mistakes: Exception was looked up as App\Services\Exception. Add use Exception; or write \Exception.

Autoloading

spl_autoload_register registers a function that PHP calls whenever it meets a class it hasn't loaded yet. The function turns the class name into a file path and requires it:

PHP src/Shop/Product.php
<?php

namespace Shop;

class Product
{
    public function __construct(public string $name, public float $price) {}
}
PHP src/Shop/Cart.php
<?php

namespace Shop;

class Cart
{
    /** @var Product[] */
    private array $items = [];

    public function add(Product $p): void { $this->items[] = $p; }
    public function total(): float { return array_sum(array_map(fn($p) => $p->price, $this->items)); }
}
PHP main.php
<?php

spl_autoload_register(function (string $class): void {
    $file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
    if (is_file($file)) {
        echo "(autoloading $class)\n";
        require $file;
    }
});

use Shop\Cart;
use Shop\Product;

$cart = new Cart();
$cart->add(new Product("Pen", 1.20));
$cart->add(new Product("Notebook", 3.50));
echo "Total: ", $cart->total(), "\n";
Output
(autoloading Shop\Cart)
(autoloading Shop\Product)
Total: 4.7

Each file is loaded only when its class is first needed — Product wasn't loaded until new Product ran.

PSR-4 and Composer

The convention used above — namespace segments map to folders, and the class name to the file name — is standardised as PSR-4. In practice you don't write the autoloader yourself: Composer generates one from a few lines of configuration (lesson 60):

JSON composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

With that, App\Models\User is loaded from src/Models/User.php, and your entry script needs just one line: require __DIR__ . '/vendor/autoload.php';.

A typical project layout

TXT
my-app/
├── composer.json
├── public/
│   └── index.php          the only file the web server exposes
├── src/                   App\ namespace (PSR-4)
│   ├── Controllers/
│   ├── Models/
│   └── Services/
├── templates/
├── tests/
└── vendor/                installed by Composer, not committed to Git

Keeping only public/ reachable from the web means your source code, configuration and libraries can never be downloaded directly by visitors.

Grouped imports and functions

Several classes from the same namespace can be imported at once, and functions and constants can be imported with use function and use const:

PHP
<?php

namespace App;

use function array_map as map;
use const PHP_EOL as EOL;

echo implode(",", map(fn($n) => $n * 2, [1, 2, 3])), EOL;
Output
2,4,6

Try it yourself

Put two classes in the Geometry namespace and use them from a script in another namespace.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
Geometry\Circle: 7.07