<< Home

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));
}
💡 Mirror your namespaces in directories. This keeps navigation easy in large codebases and matches PSR-4 expectations.

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
⚠️ Keep prefixes unique. Overlapping or ambiguous prefixes cause surprising resolutions that are hard to debug at runtime.

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"
💡 Run 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" ]
  }
}
⚠️ The 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.

ConstraintMeaningExamples
^1.4Allow non breaking updates within 1.x>=1.4.0 <2.0.0
~1.4Allow 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.0Explicit rangeTwo sided bound
dev-mainTrack a branchUnstable 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"
  }
}
💡 For libraries, prefer ^ 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" }
  ]
}
⚠️ Never commit credentials or tokens to version control. Use Composer’s config, environment variables, or your CI secret store to inject secure values at build time.