Namespaces and PSR-4 Autoloading

Reviewed & published by Brayan K

By the end of this lesson you'll organise PHP code the way every real project does — group classes under namespaces, import them with use, understand fully-qualified names and the global \ root, and let Composer's PSR-4 autoloader load each class on demand so you never write a chain of require statements again.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Declaring a Namespace

A namespace is a label that groups related classes so their names never collide with code elsewhere. You declare it with namespace App\Service; as the first statement in the file. Everything defined below — classes, functions, constants — now lives under that prefix, so the class Mailer is really App\Service\Mailer. Two libraries can each ship a Mailer and never clash.

<?php
// A namespace declaration MUST be the very first statement in the file.
// It puts every class/function/const below it under "App\Service".
namespace App\Service;

class Mailer
{
    public function send(string $to): string
    {
        return "Mail queued for " . $to;
    }
}
// Inside this file, "Mailer" is really "App\Service\Mailer".
?>

The convention is to mirror your folder structure: App\Service\Mailer sits in src/Service/Mailer.php. That mirroring is exactly what makes autoloading possible, as you'll see below.

2️⃣ Importing with use & Fully-Qualified Names

To reach a class in another namespace you can write its fully-qualified name — the whole path from the global root, with a leading backslash: \App\Service\Mailer. Writing that every time is tedious, so the use statement imports the name once, letting you write the short Mailer for the rest of the file. Remember: use only creates an alias — it does not load any file.

<?php
// In another file we want that Mailer. Its FULLY-QUALIFIED name is
// \App\Service\Mailer (leading backslash = start from the global root).
namespace App\Http;

// "use" IMPORTS the name so we can write the short "Mailer" below.
use App\Service\Mailer;

$mailer = new Mailer();          // short name, thanks to "use"
echo $mailer->send("ada@php.net") . "\n";

// Without the "use" you would write the full path every time:
$other = new \App\Service\Mailer();
echo $other->send("grace@php.net") . "\n";
?>

Both objects above are the same class — one reached by the imported short name, one by the fully-qualified path. Inside a namespace, a bare name like Mailer is resolved relative to the current namespace first, which is why the explicit leading \ matters.

3️⃣ Aliases & the Global \ Namespace

When two imports share a short name, rename one with as: use App\Service\Logger as ServiceLogger;. And remember that PHP's built-ins — DateTime, strlen, array_map — all live in the global namespace. Inside your own namespace, prefix them with a leading \ to say "look in the global root", which is also slightly faster than letting PHP fall back to it.

<?php
namespace App\Report;

// Two classes share the short name "Logger". Alias one with "as".
use App\Service\Logger as ServiceLogger;
use Psr\Log\LoggerInterface as PsrLogger;

// A leading backslash means "the GLOBAL namespace". Built-in functions
// like strlen() and DateTime live there, so \ forces the global lookup.
$now = new \DateTime();          // \DateTime, not App\Report\DateTime
$len = \strlen("hello");         // explicit global function call

echo "Length: " . $len . "\n";
echo "Year: " . $now->format("Y") . "\n";
?>

For classes the leading backslash is required (otherwise new DateTime() would look for App\Report\DateTime). For functions and constants PHP has a global fallback, but writing \strlen() is explicit and avoids surprises.

4️⃣ PSR-4 Autoloading with Composer

PSR-4 is the standard that maps a namespace prefix to a base folder. You declare it in composer.json under autoload.psr-4 — for example App\ to src/. After editing it you run composer dump-autoload to regenerate the maps. Now Composer can translate any fully-qualified class name straight into a file path.

// composer.json — map a namespace PREFIX to a base FOLDER (PSR-4).
{
    "name": "acme/blog",
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

// After editing this, regenerate the autoloader maps:
//    composer dump-autoload
//
// Now the prefix  App\  means the folder  src/  , so:
//    App\Model\User      -> src/Model/User.php
//    App\Service\Mailer  -> src/Service/Mailer.php
// Each class lives in its own file, named exactly after the class.

With that mapping in place, your entry script needs just one include — vendor/autoload.php — and then you simply use classes. Composer loads each file the first time the class is referenced, in the right order, exactly once.

<?php
// ONE line registers Composer's autoloader for the whole app.
require __DIR__ . "/vendor/autoload.php";

// From here on, just USE classes — Composer loads the right file on
// demand the first time each class is referenced. No require chains.
use App\Model\User;
use App\Service\Mailer;

$user   = new User("Ada");          // loads src/Model/User.php automatically
$mailer = new Mailer();             // loads src/Service/Mailer.php automatically

echo $mailer->send($user->email()) . "\n";
?>

This is why the one-class-per-file convention exists: each file defines a single class whose name matches the file name, so App\Model\User resolves unambiguously to src/Model/User.php.

5️⃣ Why Not Just require Everything?

Before autoloaders, every file began with a wall of require lines. That approach is fragile: move a file and the paths break, and you load classes even on requests that never use them. It is order-dependent: a class that extends another must be required after its parent. And it is repetitive: the same includes copied into file after file. Autoloading replaces all of it with a single registration line and on-demand loading.

Now you try the two core skills — declaring/importing, and wiring up the autoloader.

Fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

<?php
// 🎯 YOUR TURN — declare a namespace and import a class by its short name.
// Fill in each blank marked ___ , then run it.

// 1) Put this file under the namespace  App\Blog
___ App\Blog;          // 👉 the keyword that declares a namespace

// 2) Import  App\Service\Mailer  so you can use the short name "Mailer"
use App\Service\___;    // 👉 the class name

$mailer = new Mailer();
echo $mailer->send("reader@blog.test") . "\n";

// ✅ Expected output:
//    Mail queued for reader@blog.test
?>

One more. Finish the PSR-4 mapping and the autoloader include so the class loads on its own.

<?php
// 🎯 YOUR TURN — finish the PSR-4 mapping and the autoload include.
// (composer.json snippet + the bootstrap that uses it.)

// In composer.json the autoload section should map "App\" to "src/":
//   "autoload": { "psr-4": { "App\\": "___" } }   👉 the base folder

// In your entry script, include the generated autoloader (ONE line):
require __DIR__ . "/vendor/___";   // 👉 the file Composer writes: autoload.php

use App\Model\User;
$u = new User("Grace");
echo $u->name() . "\n";

// ✅ Expected output:
//    Grace
?>

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Namespaces & Autoloading

Syntax / CommandWhat It Does
namespace App\Service;Scope this file under App\Service (first statement)
\App\Service\MailerFully-qualified name from the global root
use App\Service\Mailer;Import a name to use its short form
use ... as Alias;Import under a different short name
\strlen() / new \DateTime()Force the global namespace lookup
"psr-4": { "App\\": "src/" }Map a prefix to a base folder in composer.json
composer dump-autoloadRegenerate the autoloader maps
require 'vendor/autoload.php';Register the autoloader (one line per app)

Mini-Challenge: A Two-Class PSR-4 App

No code is filled in this time — just a brief and an outline. Build the two namespaced classes, wire them up with PSR-4, run composer dump-autoload, then check your result against the expected output in the comments. This is exactly how every real project is structured.

<?php
// 🎯 MINI-CHALLENGE: Build a tiny two-class app wired together by PSR-4.
// No code is filled in — work from the steps below, then run it.
//
// 1. Create  src/Model/Product.php  with:
//      namespace App\Model;  class Product { ... a name and a price ... }
// 2. Create  src/Service/PriceFormatter.php  with:
//      namespace App\Service;  class PriceFormatter { format(Product $p): string }
// 3. In composer.json map the App\ prefix to src/ under autoload.psr-4,
//    then run  composer dump-autoload .
// 4. In  index.php  require "vendor/autoload.php", then
//    use both classes by their SHORT names and print a formatted price.
//
// ✅ Expected output (example):
//    Coffee Mug: $12.50

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What does the statement namespace App\Service; do at the top of a file?

  • It places every class, function, and constant in that file under the App\Service namespace
  • It imports the App\Service class
  • It creates a new folder called App/Service
  • It autoloads all files in App/Service

Answer: It places every class, function, and constant in that file under the App\Service namespace. A namespace declaration must be the first statement in a file and puts everything defined below it under that namespace, preventing name clashes.

What is a fully-qualified class name?

  • A class name written in ALL CAPS
  • Any class whose name is longer than ten characters
  • A name written from the global root with a leading backslash, e.g. \App\Service\Mailer
  • A class that has no namespace

Answer: A name written from the global root with a leading backslash, e.g. \App\Service\Mailer. A fully-qualified name starts with a leading backslash and lists the whole path from the global namespace, so it is unambiguous no matter where it is used.

Why does PHP code that calls strlen() inside a namespace sometimes use \strlen()?

  • Because strlen is deprecated
  • Because the leading backslash tells PHP to look in the global namespace, not the current one
  • Because namespaced code cannot call functions
  • Because strlen needs an argument

Answer: Because the leading backslash tells PHP to look in the global namespace, not the current one. Built-in functions live in the global namespace. A leading backslash (\strlen) forces the global lookup, which is also marginally faster than the fallback search.

What does the use statement do?

  • It includes a file like require
  • It defines a new namespace
  • It runs a function immediately
  • It imports a class, function, or constant so you can refer to it by its short name

Answer: It imports a class, function, or constant so you can refer to it by its short name. use App\Service\Mailer; lets you write Mailer instead of the full path for the rest of the file. It does not load any file by itself.

How do you create an alias for an imported class?

  • use App\Service\Mailer as M;
  • alias App\Service\Mailer M;
  • use M = App\Service\Mailer;
  • use App\Service\Mailer alias M;

Answer: use App\Service\Mailer as M;. The as keyword renames an import: use App\Service\Mailer as M; is handy when two classes share a short name.

What does PSR-4 define?

  • A coding style for indentation
  • A standard mapping from a namespace prefix to a base directory so classes load automatically
  • A database connection format
  • A way to compress files

Answer: A standard mapping from a namespace prefix to a base directory so classes load automatically. PSR-4 is the autoloading standard that maps a namespace prefix (e.g. App\) to a base folder (e.g. src/), so each class file is found from its fully-qualified name.

In composer.json, where do you declare a PSR-4 mapping?

  • Under the config key
  • Under the require key
  • Under the scripts key
  • Under the autoload key, inside a psr-4 object

Answer: Under the autoload key, inside a psr-4 object. You add it under autoload.psr-4, for example { "autoload": { "psr-4": { "App\\": "src/" } } }, mapping the App\ prefix to the src/ directory.

After editing the autoload section of composer.json, what must you run?

  • php -S localhost
  • composer install --no-dev
  • composer dump-autoload
  • composer require autoload

Answer: composer dump-autoload. composer dump-autoload regenerates the autoloader maps so Composer knows about your new namespace mapping or newly added classes.

Why is scattering require statements throughout a codebase considered bad practice?

  • It is fragile and repetitive: paths break when files move, classes load even when unused, and order matters
  • require cannot load classes at all
  • PHP forbids more than one require per file
  • require is slower than echo

Answer: It is fragile and repetitive: paths break when files move, classes load even when unused, and order matters. Manual require chains are brittle and order-dependent. An autoloader loads each class on demand, exactly once, from a single registration line.

What is the one-class-per-file convention that PSR-4 relies on?

  • Each file defines exactly one class whose name matches the file name
  • All classes go in a single big file
  • Each class must be under 100 lines
  • Every file may contain only comments

Answer: Each file defines exactly one class whose name matches the file name. PSR-4 maps a class name to a file path, so each class needs its own file named after it (User -> User.php) for the autoloader to find it.

Continue this course

Frequently asked questions

What is the difference between a namespace and a use statement?

A namespace declaration (namespace App\Service;) defines where the code in a file lives — it labels every class, function, and constant below it with that prefix so names never clash with code elsewhere. A use statement does the opposite: it imports a name defined in another namespace so you can refer to it by its short form. Crucially, use does not load any file; it is purely an alias for the rest of the current file. The autoloader is what actually finds and includes the class file when you first reference the class.

What is a fully-qualified name and when do I need the leading backslash?

A fully-qualified name starts at the global root and spells out the whole path, like \App\Service\Mailer. The leading backslash means start from the global namespace, ignoring the current one. You need it when you reference a global built-in (like \DateTime or \strlen) from inside a namespace, or when you want to be unambiguous without adding a use import. Without the backslash, PHP resolves names relative to the current namespace first, which is why a bare DateTime inside namespace App would be read as App\DateTime and fail.

How exactly does PSR-4 map a class name to a file?

PSR-4 maps a namespace prefix to a base directory in composer.json under autoload.psr-4, for example App\ to src/. To find a class, Composer strips the mapped prefix from the fully-qualified name, replaces the remaining backslashes with directory separators, and appends .php. So with App\ mapped to src/, the class App\Model\User is expected at src/Model/User.php. This is why the one-class-per-file convention matters: the file name must match the class name exactly, including capitalisation.

Why is scattering require statements everywhere a bad idea?

Manually requiring each class file is fragile and repetitive. Paths break the moment you move a file, you load classes even on requests that never use them, and the include order starts to matter (a class that extends another must be required after its parent). An autoloader fixes all of this: you register it once (require vendor/autoload.php), and from then on each class is loaded on demand, exactly once, the first time it is referenced — no matter where it lives or in what order things run.

When do I need to run composer dump-autoload?

Run composer dump-autoload whenever you change the autoload section of composer.json (adding a new psr-4 mapping, a classmap, or a files entry) so Composer regenerates vendor/autoload.php and its maps. With PSR-4 you usually do not need to re-run it just for adding a new class in an already-mapped folder, because the autoloader resolves those paths at runtime — but running it never hurts, and the optimised form composer dump-autoload -o builds a static classmap that is faster in production.

Related lessons