Composer and Packages

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Composer 2.10 (the latest 2.x release, 2.10.3) on PHP 8.5, checked on 2026-10-05. Composer installs the libraries a project depends on, resolves compatible versions of all of them, records the exact result in a lock file, and generates an autoloader. Every modern PHP project, framework and tool — Laravel, Symfony, PHPUnit, PHPStan — is installed with it, and its public registry, Packagist, hosts the ecosystem’s packages.

Installing Composer and starting a project

Composer is a single PHAR. Install it with the instructions on getcomposer.org, with php.new or Herd, from the composer Docker image, or through the operating system’s package manager. composer init creates a composer.json interactively; composer require adds a dependency and creates the file if needed.

composer --version                 # Composer version 2.10.x
composer init --name=acme/library-catalogue --type=project --no-interaction
composer require symfony/console   # adds "symfony/console": "^8.1" and installs it
composer require --dev phpunit/phpunit phpstan/phpstan

composer.json

The project’s composer.json declares what it needs; Composer works out everything else:

{
    "name": "acme/library-catalogue",
    "description": "Library catalogue tools",
    "type": "project",
    "license": "MIT",
    "require": {
        "php": ">=8.4",
        "ext-intl": "*",
        "ext-pdo": "*",
        "symfony/console": "^8.1"
    },
    "require-dev": {
        "phpunit/phpunit": "^13.0",
        "phpstan/phpstan": "^2.2"
    },
    "autoload": {
        "psr-4": { "Library\\": "src/" }
    },
    "autoload-dev": {
        "psr-4": { "Library\\Tests\\": "tests/" }
    },
    "scripts": {
        "test": "phpunit",
        "analyse": "phpstan analyse src --level=8",
        "check": ["@analyse", "@test"]
    },
    "config": {
        "sort-packages": true,
        "optimize-autoloader": true
    },
    "minimum-stability": "stable"
}

The main keys:

  • name (vendor/package), type (library, project, metapackage, composer-plugin, php-ext for PIE), license;

  • require — what is needed in production, including the PHP version and extensions (ext-intl); require-dev — tools for development and tests, skipped with --no-dev;

  • autoload / autoload-dev — class loading rules (below);

  • scripts — named commands run with composer <name>, plus hooks such as post-autoload-dump;

  • config — Composer settings for this project: platform, sort-packages, optimize-autoloader, policy;

  • repositories — additional package sources;

  • minimum-stability / prefer-stable — whether non-stable releases may be installed;

  • conflict, replace, provide, suggest, extra — advanced relationships and plugin configuration.

Version constraints

Packages follow (or should follow) semantic versioning: MAJOR.MINOR.PATCH, where only a new major version may break compatibility. Constraints describe which versions are acceptable:

Constraint Meaning Example

^8.1

caret: compatible versions, up to the next major

>=8.1.0 <9.0.0

^0.3

caret below 1.0 is narrower: minor versions may break

>=0.3.0 <0.4.0

~8.1

tilde: last given digit may increase

>=8.1 <9.0; ~8.1.2 is >=8.1.2 <8.2.0

8.1.*

wildcard

>=8.1.0 <8.2.0

>=8.0 <9.0, ^7.4 || ^8.0

ranges, AND (space or comma) and OR (||)

8.1.8

exact version (avoid in libraries)

dev-main, ^2.0@beta

a branch; a stability flag on one constraint

Use ^ for almost everything. minimum-stability (stable by default; RC, beta, alpha, dev) sets the lowest stability allowed for all packages, and prefer-stable: true picks stable releases when both are possible.

composer.lock: install versus update

Resolving constraints produces a lock file, composer.lock, with the exact version, source and checksum of every installed package, direct and transitive. The two commands that install packages treat it differently:

  • composer install installs exactly what the lock file says (or resolves and writes it if there is none). It is reproducible: CI, staging, production and every developer get the same versions.

  • composer update re-resolves the constraints, upgrades within them, and rewrites the lock file. Run it deliberately, ideally for specific packages (composer update symfony/console), and review the diff.

Commit composer.lock for applications. Libraries usually commit it too, for reproducible development, but it is ignored when the library is installed as a dependency. Never commit vendor/.

flowchart LR A["composer.json
constraints: ^8.1"] -->|composer update| R{"dependency
resolver (SAT)"} P[("Packagist and
other repositories")] --> R R --> L["composer.lock
exact versions + hashes"] L -->|composer install| V["vendor/
downloaded packages"] V --> G["vendor/autoload.php
generated autoloader"] A -->|autoload rules| G G -->|require once| APP["your application
bin/, public/index.php"]
composer install --no-dev --optimize-autoloader --no-interaction   # production / CI deploy
composer update symfony/console --with-dependencies                 # upgrade one package and its deps
composer outdated --direct                                          # what could be upgraded?
composer why symfony/string                                         # who requires this package?
composer why-not phpunit/phpunit 14.0                               # what prevents an upgrade?
composer show --tree                                                # the dependency tree

Autoloading

Composer generates vendor/autoload.php, which every entry point requires once. Autoload rules come from your own composer.json and from every installed package:

  • psr-4 — a namespace prefix mapped to a directory (the standard; see Namespaces and Autoloading);

  • classmap — scan directories or files and map every class found (legacy code, generated code);

  • files — files always included (helper functions, which cannot be autoloaded);

  • exclude-from-classmap — paths to skip.

In production, generate an optimised autoloader: composer dump-autoload --optimize (-o) converts PSR-4 rules into a class map, avoiding filesystem lookups; --classmap-authoritative (-a) additionally tells the autoloader not to look for classes outside the map; --apcu caches lookups in APCu.

$ composer dump-autoload --classmap-authoritative
Generated optimized autoload files (authoritative) containing 216 classes

Platform requirements

php and ext-* entries in require declare what the runtime needs. Composer refuses to install on a PHP that does not satisfy them, which turns "Call to undefined function intl_…" in production into an installation error. composer check-platform-reqs verifies a machine; config.platform pins a fake platform (for example the production PHP version) when resolving on a different machine, so that the lock file never contains packages the server cannot run:

{
    "config": {
        "platform": { "php": "8.4.26" }
    }
}

--ignore-platform-reqs exists for exceptional cases (building in a container without the extensions); do not make it a habit.

Scripts

Scripts give a project a uniform command vocabulary (composer test, composer check) and run on events such as post-install-cmd or post-autoload-dump. @other-script references another script, @php runs the current PHP binary, and vendor/bin is on the PATH while scripts run.

composer test           # runs "phpunit" from vendor/bin
composer check          # runs @analyse then @test
composer run-script --list

Laravel’s skeleton uses scripts for composer run dev (starting the server, queue worker, log viewer and Vite together) and to run artisan package:discover after autoload dumps.

Source: Scripts

Repositories

Packagist is the default repository. Others can be added under repositories:

  • vcs — a Git repository (private packages, forks);

  • path — a local directory, symlinked into vendor/ (developing a package and an application together);

  • composer — a private Composer repository (Private Packagist, Satis, a self-hosted registry);

  • package — a package defined inline.

{
    "repositories": [
        { "type": "vcs", "url": "https://github.com/acme/marc-parser" },
        { "type": "path", "url": "../packages/catalogue-core" }
    ],
    "require": {
        "acme/marc-parser": "^1.2",
        "acme/catalogue-core": "@dev"
    }
}

Authentication for private repositories goes in auth.json (or the COMPOSER_AUTH environment variable), never in composer.json.

Source: Repositories

Security: audit, dependency policies and bump

composer audit checks the installed packages against security advisories (from Packagist’s advisory database by default) and also reports abandoned packages and packages flagged as malware; it exits with a non-zero code when it finds problems, which makes it a natural CI step. Since Composer 2.9, updates also block installing package versions with known advisories during update, require and remove, configured since 2.10 through the unified config.policy setting (which replaces most of the older config.audit keys).

$ composer audit --locked
No security vulnerability advisories found.

$ composer bump --dev-only        # raise lower bounds in require-dev to the installed versions

composer bump raises the lower bound of each constraint to the currently installed version (^8.0 becomes ^8.1.8), which documents what you actually tested; do not run it on the require section of a library, as it narrows what users can install.

Packagist and publishing a library

To publish a library: give it a name, a license, PSR-4 autoloading and accurate require constraints; push it to a public Git repository; submit the repository URL on packagist.org; and tag releases following semantic versioning (git tag v1.2.0). Packagist updates through a webhook. Keep library constraints wide ("php": "^8.3", "psr/log": "^2.0 || ^3.0"), test against the lowest and highest versions (composer update --prefer-lowest), and never require dev tools in require.

PIE for extensions

Composer installs PHP code, not C extensions. PIE, the PHP Installer for Extensions, does the same job for extensions, reading packages of type php-ext from Packagist; run in a project, pie install installs the missing extensions that composer.json requires. See Configuration and Extensions.

Source: PIE on GitHub

Where older books and tutorials are outdated

  • PEAR (Kromann ch. 7, Wiley) is obsolete for libraries; most books besides Kromann ignore Composer entirely.

  • Composer 1 (2012-2020) is end of life; Composer 2 is much faster, parallelises downloads and supports platform checks at runtime (vendor/composer/platform_check.php).

  • composer update on servers: deploy with composer install --no-dev from the committed lock file.

  • composer audit and bump (2.4), blocking of insecure versions during updates (2.9) and the unified config.policy setting (2.10) are newer than every book.

  • PECL for extensions is being replaced by PIE.