Ouvrez un vieux projet PHP d'avant 2012 et vous tombez parfois sur un dossier lib/ rempli de bibliothèques copiées à la main, des require_once éparpillés en haut de chaque fichier, et une question sans réponse : quelle version de quoi tourne vraiment ici ? Composer a rangé tout ça. Ce billet reprend Composer depuis le début, pour un développeur qui débute et veut un modèle mental solide plutôt qu'une liste de commandes à recopier.
Au programme : le modèle mental (deux fichiers, un dossier), les commandes require, install et update avec leurs effets exacts, la syntaxe des versions (^, ~), et l'hygiène qui évite les mauvaises surprises. À la fin, vous saurez lire un composer.json, savoir quoi committer, et pourquoi lancer composer update sans filet est une mauvaise idée.
Avant Composer
Avant, on téléchargeait une bibliothèque en ZIP, on la posait dans un dossier, on ajoutait un require_once 'lib/un-truc/src/Bidule.php';, et on recommençait pour chaque dépendance de la dépendance. Mettre à jour voulait dire re-télécharger à la main et croiser les doigts. Le fameux « ça marche sur ma machine » vient en droite ligne de cette époque : personne ne savait exactement quelles versions cohabitaient dans le projet.
Composer met fin au grenier. Il télécharge, range, calcule les compatibilités et fige le résultat. Le reste de ce billet explique comment.
Le modèle mental : deux fichiers, un dossier
Tout Composer tient dans trois objets. Les comprendre, c'est déjà l'essentiel du travail.
composer.json: le manifeste de votre projet. Il déclare ce que le projet est (nom, type, licence) et ce dont il a besoin (« PHP 8.5 ou plus, Symfony 8.1 ou plus »). C'est vous qui l'écrivez, à la main ou via des commandes.composer.lock: le contrat. Il fige les versions exactes réellement installées à un instant T (« Symfony 8.1.1, ce commit précis, ce hash »). Composer le génère seul, vous n'y touchez jamais à la main.vendor/: l'entrepôt. Composer y télécharge le code de toutes les dépendances. Il se reconstruit tout seul à partir des deux fichiers, donc il ne se committe pas.
Un quatrième fichier mérite d'être cité, vendor/autoload.php, mais on y revient plus bas : c'est lui qui rend le code des dépendances utilisable sans le moindre require manuel.
Installer Composer
Composer est un exécutable PHP. On l'installe une fois, en global sur la machine, puis on vérifie que le terminal le trouve :
composer --versionL'installeur officiel (voir les références en fin de billet) s'occupe du reste sur la plupart des systèmes. Point important pour un débutant : Composer est un outil posé sur votre machine, pas une dépendance du projet. On ne le réinstalle pas à chaque clone.
Les commandes qui comptent vraiment
Composer a des dizaines de commandes. Cinq couvrent le quotidien.
Pour ajouter une dépendance :
composer require symfony/var-dumperCette commande met à jour composer.json, résout les versions compatibles, réécrit composer.lock et télécharge le code dans vendor/. Pour un outil utile seulement en développement, on ajoute --dev :
composer require --dev phpunit/phpunitPour installer ce qui est déjà déclaré :
composer installCette commande lit le composer.lock et reconstruit vendor/ à l'identique. C'est le geste d'un nouvel arrivant dans l'équipe, d'un serveur d'intégration continue, d'un déploiement. Le lock fait foi : aucune version n'est recalculée, l'environnement est le même pour tout le monde.
Pour mettre à jour :
composer updateCette commande recalcule les versions autorisées par composer.json, réécrit le lock et met à jour vendor/. Elle est puissante, et c'est précisément là qu'est le piège (section suivante).
Pour aller plus loin
La release qui ne comptait plus
Pour retirer une dépendance :
composer remove symfony/var-dumper| Commande | composer.json | composer.lock | vendor/ |
|---|---|---|---|
require foo/bar |
modifié | réécrit | installé |
install |
lu | fait foi | reconstruit |
update |
lu (contraintes) | réécrit | mis à jour |
remove foo/bar |
modifié | réécrit | nettoyé |
Un composer.json moderne, section par section
Le composer.json est le manifeste de votre application. Voici à quoi il ressemble sur un projet récent (PHP 8.5, Symfony 8.1) :
{
"type": "project",
"license": "MIT",
"require": {
"php": ">=8.5",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/orm": "^3.5",
"symfony/framework-bundle": "^8.1",
"symfony/runtime": "^8.1",
"symfony/flex": "^2.0"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^13.0",
"rector/rector": "^2.0",
"symfony/maker-bundle": "^1.50"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"symfony/flex": true,
"symfony/runtime": true
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}require et require-dev
require liste ce dont l'application a besoin pour tourner : le moteur (php), des extensions (ext-ctype), et des bibliothèques. require-dev liste ce qui ne sert qu'au développement : analyse statique (PHPStan), tests (PHPUnit), génération de code (Maker). La distinction compte au déploiement, on y revient avec --no-dev.
Sur le même sujet
jakzal/phpqa : sortir la QA Symfony de son vendor/
autoload et PSR-4
C'est ici que vendor/autoload.php prend son sens. La section autoload mappe un préfixe de namespace vers un dossier. Ici, tout ce qui commence par App\ se trouve sous src/. Concrètement, la classe App\Service\Newsletter vit dans src/Service/Newsletter.php. Une seule ligne suffit ensuite dans votre code :
require 'vendor/autoload.php';Fini les require manuels fichier par fichier. Composer génère un chargeur qui suit le standard PSR-4 et trouve chaque classe à la demande, la vôtre comme celles de vos milliers de dépendances.
config et scripts
config règle le comportement de Composer (tri des paquets, plugins autorisés). La section scripts, absente de cet exemple minimal, permet d'automatiser des tâches (vider un cache, préparer une base) déclenchées après un install ou un update.
Les versions : ^, ~ et les autres
Dans "symfony/framework-bundle": "^8.1", ce ^ n'est pas décoratif. Il dit à Composer jusqu'où il a le droit de monter tout seul. Pour le comprendre, il faut le versioning sémantique (SemVer), qui découpe une version en MAJEUR.MINEUR.PATCH. La promesse des mainteneurs : un changement cassant fait monter le chiffre MAJEUR, une fonctionnalité ajoutée le MINEUR, un correctif le PATCH.
^8.1(caret) autorise tout jusqu'à la majeure suivante exclue, soit>=8.1.0et<9.0.0. C'est le réglage par défaut, et il suffit dans la plupart des cas.~8.1.2(tilde) est plus strict :>=8.1.2et<8.2.0, donc uniquement les correctifs.~8.1autorise lui les montées mineures, comme le caret.8.1.1fige une version précise. À réserver aux cas où une version donnée casse le reste.>=8.1 <9.0décrit un intervalle explicite, utile quand^et~ne suffisent pas.
| Contrainte | Autorise | Bloque |
|---|---|---|
^8.1 |
8.1.0 à 8.9.x |
9.0.0 |
~8.1.2 |
8.1.2 à 8.1.x |
8.2.0 |
~8.1 |
8.1.0 à 8.9.x |
9.0.0 |
8.1.1 |
seulement 8.1.1 |
tout le reste |
Tout ce système repose sur une promesse : que les mainteneurs ne cassent rien en mineur ni en patch. Quand la promesse n'est pas tenue, le composer.lock versionné est votre filet. Il rejoue les versions exactes qui, elles, fonctionnaient.
install contre update : le piège qui peut vite être fatal
La confusion entre install et update est l'erreur numéro un des débuts. Les deux touchent vendor/, mais pas de la même façon.
install applique le lock tel quel : reproductible, sans surprise. update ignore le lock, recalcule les versions et le réécrit. Vous pouvez vous coucher avec une prod verte et vous réveiller avec trois dépendances passées à une version majeure que personne n'a testée.
La règle tient en une phrase : sur un serveur, toujours install. Le update se fait sur une machine de développement, à froid, avec une suite de tests derrière. Et quand on met à jour, on cible :
# On ne met à jour qu'un paquet, pas tout l'univers
composer update symfony/console
# Répétition générale : montre ce qui bougerait, ne touche à rien
composer update --dry-runDéployer proprement : --no-dev
Au moment du déploiement, on ne veut ni PHPUnit ni Rector sur le serveur. La commande de production le dit :
composer install --no-dev --optimize-autoloader--no-dev saute tout ce qui est dans require-dev. --optimize-autoloader précalcule la correspondance classe vers fichier, ce qui accélère le chargement en production. Résultat : une installation plus légère et plus rapide.
L'hygiène : outdated, audit, bump
Composer ne sert pas qu'à installer. Depuis la version 2.4, il aide aussi un projet à vieillir proprement.
# Qui est en retard ? (dépendances directes uniquement)
composer outdated --direct
# Des failles de sécurité connues dans mes versions installées ?
composer audit
# Aligner les contraintes du json sur ce qui est réellement installé
composer bumpoutdated liste ce qui a pris du retard. audit confronte votre composer.lock aux avis de sécurité publiés et sort en erreur si une dépendance vulnérable est installée. bump resserre les contraintes du composer.json sur les versions en place, une fois la mise à jour digérée.
Le fichier qui dit ce qu'un projet vaut
Composer va bien plus loin : dépôts privés, dépôts path et vcs, plugins, scripts d'événements avancés, gestion de monorepo, résolution multi-plateforme. Chacun mérite son propre billet. Ici, on est resté sur ce qui fait tenir un projet debout au quotidien, celui d'un développeur qui débute et veut des bases saines.
composer.json porte bien son surnom de manifeste. En une trentaine de lignes, il déclare ce qu'un projet est, ce dont il dépend, et jusqu'où il s'autorise à bouger. Ce petit fichier a fait plus pour la fiabilité de PHP que bien des débats de syntaxe : il a rendu un projet reproductible, d'une machine à l'autre, d'une année à l'autre.
La prochaine fois que vous clonez un dépôt, ouvrez son composer.json avant le code. Il vous dira, en un coup d'œil, si le projet a été soigné ou laissé au grenier. Le vôtre raconte quoi ?
Vous avez suivi ?
Une coquille, une erreur dans ce billet ? Signale-la-moi.
Ce billet est publié sous licence Creative Commons BY-NC-SA 4.0 (attribution, pas d'usage commercial, partage dans les mêmes conditions).