Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 49 of 54

Namespaces, autoloading and Composer

Organise classes with namespaces and use statements, load them automatically with a PSR-4 autoloader, and install and autoload packages with Composer.

14 minObject-oriented PHPUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonStructure a project with namespaces and PSR-4 autoloading, and install a package with Composer.

Two classes called Product

As a project grows and uses libraries written by others, names collide. Your shop has a Product class, a payment library has its own Product, and an analytics package has a third. PHP cannot have two classes with the same name, so namespaces give every class a full, unique name, much like folders give files unique paths: App\Models\Product, Payments\Product, Analytics\Product.

Declaring a namespace

A namespace statement at the top of a file puts everything in that file inside the namespace. By convention, the namespace mirrors the folder the file lives in:

php
<?php
declare(strict_types=1);

namespace App\Models;

class Product
{
    public function __construct(public string $name, public float $price) {}
}
php
<?php
declare(strict_types=1);

namespace App\Services;

use App\Models\Product;

class PriceFormatter
{
    public function format(Product $product): string
    {
        return $product->name . ': ₹' . number_format($product->price, 2);
    }
}

The class's full name is now App\Models\Product. In PriceFormatter.php, the line use App\Models\Product; creates a short alias, so the rest of the file can write just Product. use does not load anything; it only says which full name the short one stands for.

If two imported classes have the same short name, rename one with as: use Payments\Product as PaymentProduct;. A leading backslash means "from the root": \DateTimeImmutable is PHP's built-in class, even inside a namespace.

Loading classes automatically

So far, each file needed a require for every class it used. With dozens of classes, that becomes unmanageable. Autoloading asks PHP to call a function whenever an unknown class is used for the first time; the function works out which file contains it and loads it.

The standard rule, PSR-4, maps a namespace prefix to a base folder. With App\ mapped to src/, App\Models\Product is in src/Models/Product.php. A PSR-4 autoloader is only a few lines:

php
<?php
spl_autoload_register(function (string $class): void {
    $prefix = 'App\\';
    if (!str_starts_with($class, $prefix)) {
        return; // not one of ours; let other autoloaders try
    }
    $relative = substr($class, strlen($prefix));
    $file = __DIR__ . '/src/' . str_replace('\\', '/', $relative) . '.php';
    if (is_file($file)) {
        require $file;
    }
});

Now the entry script needs only one require, and classes load as they are used:

php
<?php
declare(strict_types=1);

require __DIR__ . '/autoload.php';

use App\Models\Product;
use App\Services\PriceFormatter;

$formatter = new PriceFormatter();
echo $formatter->format(new Product('Mechanical Keyboard', 89.5)) . "\n";
echo Product::class . "\n";
var_dump(class_exists('App\Models\Order'));
result
Mechanical Keyboard: ₹89.50
App\Models\Product
bool(false)

Product::class gives the full name. class_exists() triggered the autoloader for App\Models\Order, found no file, and returned false. In practice you will not write the autoloader yourself: Composer generates one.

Composer

Composer is PHP's package manager. It downloads libraries (from Packagist, the public package registry), installs them into a vendor/ folder, and writes an autoloader for them and for your own code. Install it from getcomposer.org, then check it in a terminal:

bash
composer --version

A project describes itself and its dependencies in composer.json. The autoload section tells Composer about your own PSR-4 namespace:

json
{
    "name": "himanshu/shop",
    "require": {
        "php": ">=8.2",
        "vlucas/phpdotenv": "^5.6"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

The everyday commands:

bash
composer init                         # create composer.json interactively
composer require vlucas/phpdotenv     # add a package and install it
composer install                      # install exactly what composer.lock lists
composer update                       # upgrade packages within the allowed versions
composer dump-autoload                # regenerate the autoloader after changing autoload rules
composer audit                        # check installed packages for known security problems

After installing, a single line at the top of your entry script loads everything, packages and your own classes alike:

php
<?php
require __DIR__ . '/vendor/autoload.php';

use App\Services\PriceFormatter;
use Dotenv\Dotenv;

Dotenv::createImmutable(__DIR__)->load();   // reads settings from a .env file
$formatter = new PriceFormatter();

Versions and the lock file

"^5.6" is a version constraint: any 5.x version from 5.6 up, but not 6.0, which might contain breaking changes. Packages follow semantic versioning: MAJOR.MINOR.PATCH, where a new major version may break things.

composer require and composer update write the exact versions they chose into composer.lock. Commit both composer.json and composer.lock to version control, and do not commit vendor/. Anyone (including your server) runs composer install to get exactly the same versions. Run composer audit regularly: this site's own deployment checks run it to catch known vulnerabilities in dependencies.

A typical project layout

text
shop/
├── composer.json
├── composer.lock
├── public/            ← the web root: only this folder is reachable from a browser
│   └── index.php      ← requires ../vendor/autoload.php
├── src/               ← your classes, namespace App\
│   ├── Models/
│   └── Services/
├── templates/
├── vendor/            ← installed by Composer, not committed
└── .env               ← secrets, not committed

Laravel, Symfony and most modern PHP projects follow this shape. Keeping src/, vendor/ and .env outside the web root means a browser can never request them.

Common mistakes

  • Forgetting the namespace line, or a namespace that does not match the folder, so the autoloader cannot find the class.
  • Writing a single backslash in a JSON or double-quoted string: in composer.json the prefix is "App\\".
  • Committing vendor/ instead of composer.lock.
  • Forgetting composer dump-autoload after changing the autoload rules.

Exercise

Add a class App\Models\Order in src/Models/Order.php with an id and an array of Product objects, and a method total(): float. Using the autoloader above, create an order with two products and print its total.

Show one possible answer
php
<?php
declare(strict_types=1);

namespace App\Models;

class Order
{
    /** @param Product[] $products */
    public function __construct(public int $id, private array $products) {}

    public function total(): float
    {
        return array_sum(array_map(fn(Product $p) => $p->price, $this->products));
    }
}
php
<?php
declare(strict_types=1);

require __DIR__ . '/autoload.php';

use App\Models\Order;
use App\Models\Product;

$order = new Order(1042, [new Product('Mechanical Keyboard', 89.5), new Product('Yoga Mat', 24.99)]);
echo "Order {$order->id}: {$order->total()}\n";
result
Order 1042: 114.49

Because Product is in the same namespace as Order, the Order file can use the short name without a use statement.

Key points

  • A namespace gives classes a full name, App\Models\Product, so names from different libraries cannot clash.
  • use App\Models\Product; lets you write the short name Product in a file.
  • PSR-4 autoloading maps namespaces to folders, so PHP finds the file for App\Models\Product in src/Models/Product.php automatically.
  • Composer installs packages into vendor/, records them in composer.json and composer.lock, and generates vendor/autoload.php.

Check yourself

1. What does use App\Services\Mailer; do?
2. Under PSR-4 with App\ mapped to src/, where is App\Models\Order?
3. Which file should you commit so everyone installs exactly the same package versions?
4. Which single require line makes every Composer package and your PSR-4 classes available?