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
declare(strict_types=1);
namespace App\Models;
class Product
{
public function __construct(public string $name, public float $price) {}
}<?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
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
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'));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:
composer --versionA project describes itself and its dependencies in composer.json. The autoload section tells Composer about your own PSR-4 namespace:
{
"name": "himanshu/shop",
"require": {
"php": ">=8.2",
"vlucas/phpdotenv": "^5.6"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}The everyday commands:
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 problemsAfter installing, a single line at the top of your entry script loads everything, packages and your own classes alike:
<?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
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 committedLaravel, 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.jsonthe prefix is"App\\". - Committing vendor/ instead of
composer.lock. - Forgetting composer dump-autoload after changing the
autoloadrules.
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
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
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";Order 1042: 114.49Because 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.