
Download the installer, compare its SHA-384 hash with the one published at composer.github.io/installer.sig, then run it into /usr/local/bin/composer. Commit composer.lock, ignore vendor/. In production, composer install --no-dev --optimize-autoloader, never composer update and never sudo.
Composer is the dependency manager for PHP. It installs the libraries a project needs, resolves their own dependencies, locks versions and generates class autoloading. This tutorial covers installation on all three systems, the day-to-day commands and the mistakes that cost time. Verified with Composer 2.10.3 on PHP 8.5.10.
Installing, with the signature checked
The recipe you see everywhere is curl -sS https://getcomposer.org/installer | php. It runs a downloaded file on the spot, with nothing checked. The official procedure compares the SHA-384 hash of the script with the one published by the project before running it.
#!/usr/bin/env bash
set -euo pipefail
ATTENDU=$(curl -sS https://composer.github.io/installer.sig)
curl -sS https://getcomposer.org/installer -o composer-setup.php
CALCULE=$(php -r "echo hash_file('sha384', 'composer-setup.php');")
if [ "$ATTENDU" != "$CALCULE" ]; then
>&2 echo 'ERREUR : signature invalide, ne pas exécuter'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
composer --versionComposer version 2.10.3 2026-08-27 13:34:23The two hashes match, so the installer is genuine. Thirty seconds of extra work, against running an arbitrary script with the rights of your account.
Windows
Download and run Composer-Setup.exe. The installer finds PHP, sets up the PATH and updates itself.
macOS
The script above works as it is. With Homebrew:
brew install composerOne thing about older tutorials: they tell you to add an alias to ~/.bash_profile. Since macOS Catalina (2019) the default shell is zsh, and that file is no longer read. The file to edit is ~/.zshrc. And the alias is not needed at all if composer.phar is installed in /usr/local/bin/composer with the execute bit set.
Linux
sudo apt-get update
sudo apt-get install -y php-cli unzip curl
# then the verification script aboveThe composer package in the Debian and Ubuntu repositories is often several versions behind. The official installer is the better option.
Starting a project
mkdir mon-projet && cd mon-projet
composer init # interactive questionnaire
composer require monolog/monologThis command creates or updates three things:
composer.json, what you ask for. Goes into version control.composer.lock, the exact versions installed, down to the commit. Goes into version control too: this is what guarantees that the whole team and production run the same code.vendor/, the downloaded files. Not versioned, put it in.gitignore.
/vendor/install or update: the distinction that matters
| Command | What it does | Where to use it |
|---|---|---|
composer install | Installs exactly what composer.lock says | Production, continuous integration, joining a team |
composer update | Looks for newer versions and rewrites composer.lock | Development machine, never in production |
Running composer update on a production server installs versions nobody has tested. It is behind a remarkable number of failed releases.
# Update a single package
composer update monolog/monolog
# Update a package and its dependencies
composer update monolog/monolog --with-dependencies
# See what would change, without changing anything
composer update --dry-runVersion constraints
{
"require": {
"php": ">=8.2",
"monolog/monolog": "^3.5",
"symfony/console": "~6.4.0",
"vendor/paquet": "2.1.3"
}
}| Notation | Allows | Rejects |
|---|---|---|
^3.5 | 3.5, 3.6, 3.99 | 4.0 |
~6.4.0 | 6.4.0, 6.4.9 | 6.5.0 |
6.4.* | 6.4.0 to 6.4.99 | 6.5.0 |
2.1.3 | 2.1.3 only | everything else |
The ^ is the right default: it allows fixes and additions, and rejects breaking changes. Pinning an exact version looks careful, but it also blocks security patches.
Declaring the PHP version in require stops you installing a package that will not run on the server. And when the development machine does not have the same version as production, config.platform resolves dependencies for the target version:
{
"config": {
"platform": {
"php": "8.4.0"
}
}
}Autoloading
This is the most useful thing Composer does, and the last one people discover. The PSR-4 standard maps a namespace prefix to a directory.
{
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Tests\\": "tests/"
},
"files": [
"src/fonctions.php"
]
}
}Backslashes are doubled in a JSON file. It is the most common mistake in this file, and it stays silent right up to the moment Composer refuses to read it:
"App\": "src/" -> JSON INVALIDE (l'antislash échappe le guillemet)
"App\\": "src/" -> correctWith this configuration, the App\Personnages\Guerrier class is looked for in src/Personnages/Guerrier.php. The path follows from the name, with backslashes in the code and forward slashes in the directories.
<?php
declare(strict_types=1);
namespace App\Personnages; // backslashes, never forward slashes
final class Guerrier
{
// …
}<?php
require __DIR__ . '/vendor/autoload.php';
use App\Personnages\Guerrier;
$conan = new Guerrier();After any change to the autoload section:
composer dump-autoloadNo comments in composer.json
JSON has no comments. A // slipped into the file makes it unreadable:
In JsonFile.php line 398:
"./composer.json" does not contain valid JSON
Lexical error on line 5. Comments are not allowed.The composer validate command checks the file before the problem turns up somewhere else.
Checking for vulnerabilities
composer audit compares the installed packages against the security advisory database of the PHP ecosystem.
composer auditNo security vulnerability advisories found.It is a command to put in continuous integration, after composer install: without a vendor/ folder it only answers “No installed packages found”. It returns a non-zero exit code when an advisory concerns the project.
Spotting abandoned packages
Composer reports packages whose author has declared them abandoned, at install time. The warning often goes unnoticed in the flow of output:
Package facebook/php-sdk is abandoned, you should avoid using it.
Use facebook/graph-sdk instead.An abandoned package gets no more fixes, security ones included. That message deserves a pause rather than being left to scroll past.
Deploying to production
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist| Option | Effect |
|---|---|
--no-dev | Does not install development tools (tests, static analysis) |
--optimize-autoloader | Generates a full class map, with no disk lookup at runtime |
--no-interaction | Asks no questions |
--prefer-dist | Downloads archives instead of cloning the repositories |
And one rule worth repeating: never run Composer with sudo. The files in vendor/ would belong to root, the web server could no longer read them, and the Composer cache would end up in /root/.composer. If the global install asks for sudo, that is only to write into /usr/local/bin, once.
Day-to-day commands
# What is installed
composer show
composer show monolog/monolog # details of one package
composer show --tree # dependency tree
# What could be updated
composer outdated
composer outdated --direct # only your direct dependencies
# Why is this package here?
composer why psr/log
composer why-not symfony/console 7.0 # what is blocking an upgrade
# Remove a package
composer remove monolog/monolog
# Repair a broken install
rm -rf vendor composer.lock && composer installTwo Composer 1 flags are still doing the rounds in tutorials. composer show -i now returns a warning, since installed packages are what you get by default:
You are using the deprecated option "installed". Only installed packages are
shown by default now. The --all option can be used to show all packages.And composer --V does not exist, it is -V or --version:
The "--V" option does not exist.Package sources off the beaten track
{
"require": {
"moi/mon-paquet": "dev-main"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/moi/mon-paquet"
}
]
}To work on a package alongside the project that uses it, a repository of type path creates a symbolic link: your changes show up straight away, with no reinstall.
{
"repositories": [
{
"type": "path",
"url": "../mes-paquets/mon-paquet",
"options": {
"symlink": true
}
}
]
}Scripts
{
"scripts": {
"test": "phpunit",
"analyse": "phpstan analyse src --level=8",
"verifier": [
"@analyse",
"@test"
]
}
}composer verifierThat saves everyone remembering the paths into vendor/bin, and gives the whole team the same commands.
Key points
- Check the hash of the installer before running it.
- Commit
composer.lock, ignorevendor/. installin production,updateonly in development.- Double the backslashes in
composer.json, and no comments. composer auditin continuous integration, neversudo.
Composer is the way into most libraries: PHPMailer for sending email and Ratchet for a WebSocket server both install with composer require.
What comes next: Composer best practices, the basics of OOP in PHP to make the most of autoloading, and the Web development hub.
Common errors
curl … | php runs a downloaded file with nothing checked. The official procedure compares its SHA-384 hash before running it."App": "src/" is invalid JSON: the backslash escapes the quote. You need two.Lexical error on line 5. Comments are not allowed. JSON does not accept comments.composer install, which follows composer.lock.vendor/ then belong to root and the web server can no longer read them.The "--V" option does not exist. and You are using the deprecated option "installed". Both flags come from Composer 1.~/.zshrc.

