Chapter 10: Namespaces, Autoloading, and Composer
Namespaces keep code organized and avoid symbol collisions. Autoloading turns class names into file paths so that you do not write require calls everywhere. Composer ties the ecosystem together with dependency management, scripts, and publishing. This chapter shows how to declare namespaces, configure PSR-4 autoloading, work with composer.json, optimize autoloading, express version constraints, and publish public or private packages.
Declaring and Using Namespaces
A namespace groups related classes, interfaces, traits, functions, and constants. Declare the namespace once at the top of the file; then reference names inside or import external names with use. Namespaces prevent collisions and make intent clear.
Declaring a file level namespace
Place the declaration at the first line of the file before any code output. The name should reflect your project and directory layout.
<?php
namespace App\Service;
class Mailer {
public function send(string $to, string $msg): void {
// send …
}
}
Importing with use and aliasing with as
Import long names to keep code readable. Aliasing helps when two imported names would clash. You can import classes, functions, and constants.
<?php
namespace App\Controller;
use App\Service\Mailer;
use Vendor\Package\Http\Client as HttpClient;
use function Vendor\Str\join_words as joinWords;
use const Vendor\Config\TIMEOUT;
class SignupController {
public function __construct(private Mailer $mailer, private HttpClient $http) {}
public function handle(): void {
$msg = joinWords(["Welcome", "aboard"]);
$this->mailer->send("user@example.com", $msg);
$this->http->get("https://example.com", timeout: TIMEOUT);
}
}
Referring to global names with fully qualified paths
Prefix with a leading backslash to bypass the current namespace. This is useful when calling built in classes or functions from inside your own namespace.
<?php
namespace App\Util;
function uuid(): string {
return \bin2hex(\random_bytes(16));
}
PSR-4 Autoloading
PSR-4 defines how fully qualified class names map to file paths. Composer reads your mappings and generates an autoloader that resolves classes on demand. This removes the need for manual require statements and keeps startup fast.
Understanding the PSR-4 name to path mapping
With PSR-4 the prefix Acme\Utils\ mapped to the base directory src/ means Acme\Utils\Image\Filter is loaded from src/Image/Filter.php. Case must match the class declaration on case sensitive filesystems.
{
"autoload": {
"psr-4": {
"Acme\\Utils\\": "src/"
}
}
}
Autoloading multiple roots and testing code
You can map several prefixes and include a separate map for development only code. Use autoload-dev to load test namespaces without polluting production.
{
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Domain\\": "domain/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}
Regenerating the autoloader after changes
When you add classes or update mappings, regenerate the autoloader. The optimized variant builds a class map for faster lookups in production.
composer dump-autoload
composer dump-autoload -o
Composer Basics and composer.json
Composer manages dependencies and autoloading for PHP projects. The composer.json file declares package metadata, requirements, autoload rules, scripts, and more. Composer writes composer.lock to pin exact versions for reproducible installs.
Creating a new project definition
Initialize in an empty directory or an existing repository. Provide a package name in the form vendor/name, then answer prompts or edit the file later.
composer init
# Fill fields, or generate a minimal file and edit …
Declaring dependencies and development tools
Use require for runtime dependencies and require-dev for tools needed only during development. Composer installs everything into vendor/.
{
"name": "acme/app",
"require": {
"guzzlehttp/guzzle": "^7.9",
"psr/log": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"phpstan/phpstan": "^2.0"
}
}
Committing composer.lock for repeatable builds
Always commit the lock file for applications so every environment installs the same versions. For libraries, commit it if your team wants deterministic local testing; publishing ignores the lock by design.
git add composer.json composer.lock
git commit -m "Track dependencies"
composer install in CI for applications. Use composer update only when you intend to refresh versions and review changes.
Scripts, Autoload Optimization, and Classmaps
Composer can run lifecycle scripts, optimize autoloading for production, and include classmaps for legacy directories. These features smooth local workflows and improve runtime performance.
Defining lifecycle scripts in scripts
Attach commands to Composer events such as post-install-cmd or define custom script names. Scripts run in project context with Composer’s vendor binaries on the path.
{
"scripts": {
"test": "phpunit",
"lint": "php -l src",
"post-install-cmd": [
"@test",
"php bin/warm-cache.php"
]
}
}
Optimizing autoloading for deployment
Use optimization flags to preload a complete classmap and collapse PSR-0 or PSR-4 lookups. This reduces filesystem checks during production startup.
composer install --no-dev --classmap-authoritative
# or
composer dump-autoload -o --apcu
Mixing PSR-4 with classmap and files autoloaders
When dealing with legacy or non namespaced code, include specific directories in the classmap or load helper files that define functions or constants.
{
"autoload": {
"psr-4": { "App\\": "src/" },
"classmap": [ "legacy/", "database/seeds/" ],
"files": [ "src/helpers.php" ]
}
}
files autoloader executes files on every request. Keep that list small to avoid unnecessary work during bootstrap.
Semantic Versioning and Constraints
Semantic Versioning uses MAJOR.MINOR.PATCH. Increment major when you break compatibility; increment minor when you add features without breaking; increment patch for bug fixes. Composer constraints express the compatible range that your package accepts.
Expressing version intent with constraint operators
Choose an operator that matches your tolerance for updates. The caret is most common for libraries that follow SemVer strictly; the tilde is narrower; wildcards and inequalities are available when needed.
| Constraint | Meaning | Examples |
^1.4 | Allow non breaking updates within 1.x | >=1.4.0 <2.0.0 |
~1.4 | Allow updates up to next minor | >=1.4.0 <1.5.0 |
1.4.* | Any patch in 1.4 | >=1.4.0 <1.5.0 |
>=2.0 <3.0 | Explicit range | Two sided bound |
dev-main | Track a branch | Unstable development |
Controlling stability with flags
Use minimum-stability and prefer-stable to admit pre releases selectively. You can also mark a single requirement as @beta or @dev without weakening the whole project.
{
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"vendor/feature": "^2.1@beta"
}
}
^ constraints so downstream users get bug fixes automatically while staying within the same major series.
Publishing and Private Packages
Publishing makes your library available through Packagist so others can install it with Composer. Private packages use custom repositories or authenticated access so you can share code within a team.
Preparing a library for Packagist
Give the package a unique vendor/name, add a description, license, and keywords, then tag releases in Git. Packagist reads tags to determine versions.
{
"name": "acme/logger",
"description": "Structured logging utilities",
"type": "library",
"license": "MIT",
"keywords": ["logging","psr-3"],
"autoload": { "psr-4": { "Acme\\Logger\\": "src/" } },
"require": { "php": "^8.2", "psr/log": "^3.0" }
}
git tag v1.0.0
git push --tags
# Submit repository URL on Packagist …
Using private repositories for internal code
Add a repositories section that points to a VCS host, a local path, or an artifact directory. Authenticate with tokens stored in Composer’s config rather than in composer.json.
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/acme/private-lib" },
{ "type": "path", "url": "../shared/*", "options": { "symlink": true } }
],
"require": {
"acme/private-lib": "^1.2",
"acme/shared-tool": "^0.4"
}
}
# Store tokens locally
composer config --global github-oauth.github.com <token>
composer config --global http-basic.git.example.com username password
Mirroring and aggregating with a private index
Tools such as Satis or Private Packagist generate a private repository index that mirrors selected packages. Point Composer at that index for faster installs and controlled provenance.
{
"repositories": [
{ "type": "composer", "url": "https://packages.acme.internal" }
]
}