Composer
Composer est un outil de gestion de dépendances pour PHP.
Gestion de dépendances
Lors de l'écriture d'une application, nous aurons souvent besoin de nombreuses fonctionnalités annexes à la réalisation du produit final.
Ainsi, je veux peut-être envoyer des emails, mais je n'ai pas envie de passer 25 jours à la réalisation d'un module complet d'envoi d'email, ou encore je veux faire de l'upload d'images pour mes utilisateurs, mais je ne veux pas chercher, concevoir et réaliser pendant des dizaines de jours un module de manipulation d'images. Les exemples sont nombreux.
On peut alors s'appuyer sur des modules réalisés par la communauté, et les intégrer à notre application pour les utiliser.
Le même principe existe dans de nombreux langages : Java, C#, Rust, Javascript...
La grande majorité de ces modules, comme des packages applicatifs, sont réalisés et maintenus par des communautés de développeurs open-source, sur des plateformes comme Github, Gitlab ou Bitbucket.
Ils sont ensuite publiés sur des repositories, des dépôts recensant tous les packages existants pour un langage donné.
Pour Composer, on trouvera ces packages sur le site Packagist. C'est le dépôt principal de packages pour PHP.
Installation
Tout système
Pour télécharger et installer Composer, une page dédiée est disponible sur le site. L'installation via la ligne de commande est très rapide et facile à utiliser.
Pour aller encore plus vite et pouvoir utiliser Composer depuis n'importe quel endroit sur sa machine (en ligne de commande), on pourra utiliser les options --install-dir pour viser directement un répertoire inclus dans le PATH et --filename pour nommer l'exécutable composer directement, plutôt que composer.phar :
php composer-setup.php --install-dir /usr/bin --filename composer
Windows
Pour Windows, on a même un installeur exécutable disponible sur le site.
Prise en main
Une fois installé, on peut utiliser Composer avec la ligne de commande en entrant composer [options] [arguments].
Par exemple, pour vérifier l'installation, on peut tout à fait utiliser la commande suivante pour afficher la version :
composer --version
On pourra également exécuter composer list ou bien composer tout court pour afficher toutes les commandes disponibles.
Mise à jour
Pour mettre à jour l'outil Composer lui-même, la commande est également très simple :
composer self-update
Un projet avec Composer
Plus qu'un gestionnaire de dépendances
Nous allons à présent initialiser un projet avec Composer, et voir qu'il s'agit de plus qu'un gestionnaire de dépendances.
Si on se place dans un dossier vide et qu'on tape la commande composer init, alors un assistant commence à nous poser des questions :
Nom du package
Le format demandé pour le nom du package est <vendor>/<name>.
- La partie
vendorcorrespond à l'éditeur, ou l'organisation qui réalise le package. Par exemple, on trouvera des dizaines et des dizaines de packages avecsymfonycomme vendor, car avant d'être un framework, Symfony est avant tout un ensemble de composants PHP réutilisables... - Le
name, quant à lui, est beaucoup plus rapide à comprendre : c'est le nom de notre package
Par exemple, moi je vais mettre ld-web/php.
On pourra retrouver le même type de fonctionnement dans d'autres gestionnaires, par exemple dans NPM, à la différence que le vendor est préfixé par @ (@angular/cli par exemple)
Autres informations
Les autres informations sont moins importantes, surtout si on ne souhaite pas réaliser et publier un package.
En revanche, on répondra "no" à la question "Voulez-vous définir vos dépendances de manière interactive ?", et de même pour la question suivante ("dev dependencies").
On va ensuite passer la section suivante avec "n", puisqu'on va écrire nous-mêmes notre configuration d'autoload PSR-4.
Finalement, on se retrouve avec un fichier basique, au format json, décrivant la configuration de notre projet.
Nous pouvons confirmer la génération.
Le fichier composer.json
Finalement, l'outil nous a permis de générer un fichier de configuration décrivant notre projet.
Au-delà des dépendances, on y trouvera le nom, la description, l'auteur, la licence, et d'autres informations autres que les dépendances du projet.
Ajout d'une dépendance
Supposons, pour le moment de manière fictive, que nous souhaitions envoyer des emails avec notre application.
Nous pouvons rechercher sur le site packagist.org le mot-clé mail, qui nous donne en retour la liste de potentiels packages correspondant à notre recherche.
On voit que le premier résultat, swiftmailer/swiftmailer, a été abandonné, au profit du package symfony/mailer. Ceci peut nous donner une bonne indication pour installer un package qui est encore maintenu et mis à jour régulièrement.
Pour installer la dépendance, nous allons utiliser la commande suivante :
composer require symfony/mailer
Composer va alors rechercher sur le dépôt de packages le composant correspondant, et l'installe.
Il va également procéder à tout un tas de modifications au sein de notre projet.
la section require
En premier lieu, dans le fichier composer.json, on remarque que la section require, qui était vide, contient à présent une entrée :
{
"require": {
"symfony/mailer": "^6.2"
}
}
Composer a enregistré le composant symfony/mailer en tant que dépendance de notre projet, et a indiqué une version correspondante. En réalité, il s'agit d'un intervalle acceptable de versions installables au sein de notre projet, mais nous allons détailler ça un peu plus loin. Pour le moment, nous analysons ce qui a changé dans l'arborescence de notre projet.
La version peut varier suivant la date d'installation du package
le dossier vendor
Par ailleurs, Composer a créé un nouveau dossier vendor à la racine de notre projet.
Si on le déplie, on remarque que beaucoup de dossiers ont été créés. On peut alors facilement naviguer dans le dossier symfony, puis mailer pour trouver, en réalité, le code du composant symfony/mailer.
vendor ?Nous voyons, dans le dossier mailer, qu'il y a également un fichier composer.json. Ce fichier contient les dépendances dont le composant Mailer a besoin pour fonctionner. Voilà pourquoi Composer a également téléchargé et installé ces autres composants
Prenons par exemple le composant psr/container, donc dans le dossier vendor/psr/container.
Si nous voulons savoir pourquoi il est installé, nous pouvons le demander à Composer :
composer depends psr/container
Composer nous indique que le composant symfony/service-contracts requiert la présence du composant psr/container.
Mais d'où vient symfony/service-contracts ?
composer depends symfony/service-contracts
Composer nous répond que symfony/mailer a besoin de symfony/service-contracts.
C'est ainsi toute une arborescence de dépendances qui a été installée par Composer, et ce à l'installation d'un package seulement.
Toutes les dépendances du projet se trouveront donc dans le dossier vendor
Le fichier composer.lock
Enfin, Composer a également créé un fichier composer.lock, également à la racine du projet.
Si on l'ouvre, on voit alors un fichier de plusieurs centaines de lignes, au format json. Il est bien indiqué que ce fichier a été créé automatiquement.
On remarque une section packages. Cette section contient les versions précises des packages installés dans le dossier vendor.
Si on recherche avec Ctrl+F le composant symfony/mailer, on pourra facilement trouver sa version (6.2.5).
Mais dans le fichier composer.json, la version indiquée est ^6.2. Parlons à présent des versions des composants.
Le versioning sémantique (Semver)
Le versioning sémantique (ou semver) est divisé en 3 parties X.Y.Z :
- X : Version majeure (Cette version introduit des changements significatifs par rapport à la version précédente, certaines fonctionnalités qui précédaient sont supprimées par exemple)
- Y : Version mineure (On peut introduire de nouvelles choses dans une version mineure par exemple, en s'assurant que tout ce qui existe déjà continue de fonctionner)
- Z : Version de patch (Correctifs de bugs et sécurité)
Cela permet aux équipes qui maintiennent des packages d'effectuer des montées de versions graduelles, et d'organiser leurs sorties. On se doute qu'un patch de sécurité provoquera probablement un passage de 6.2.5 à 6.2.6 tandis que l'introduction de toutes nouvelles classes avec de nouvelles fonctionnalités, ou encore une réécriture de code existant qui changerait la manière de l'utiliser, provoquerait un passage de 6.2.5 à 7.0.0.
Les contraintes de version dans Composer
Avec le versioning sémantique se pose alors la question de comment gérer toutes ces versions. C'est exactement le rôle d'un gestionnaire de dépendances.
composer.json
Dans le fichier composer.json, Composer va indiquer, pour chaque package dont notre projet dépend, une contrainte de version, ou encore un intervalle de versions acceptables.
Pour ce faire, il va utiliser une syntaxe particulière avec des opérateurs qui ont un sens. Prenons encore une fois notre symfony/mailer :
| Contrainte | Intervalle | Explication |
|---|---|---|
6.2.1 | = 6.2.1 | Je veux précisément la version 6.2.1, et aucune autre |
^6.2 | >= 6.2.0, < 7.0.0 | Je reste ouvert aux évolutions du package car l'intervalle signifie que je peux accueillir plusieurs versions différentes. Bien veiller aux signes >= et <, ici on indique bien qu'on exclut la version majeure 7.0.0 car c'est strictement inférieur |
6.2.* | >= 6.2.0, < 6.3.0 | Dans ce cas, je suis ouvert aux mises à jour, mais seulement pour les versions mineures en-dessous de la version 6.2 |
~6.2 | >= 6.2.0, < 7.0.0 | Avec ~, j'autorise la dernière partie spécifiée à augmenter (ici version mineure). Donc quelque soit la version mineure, j'autorise les mises à jour |
~6.2.0 | >= 6.2.0, < 6.3.0 | De même, avec ~, j'autorise la dernière partie spécifiée à augmenter (ici version de patch) |
* | * | Toutes les versions sont acceptables |
composer.lock
Étant donné que le fichier composer.json renseigne des intervalles de versions, il faut un moyen pour Composer de retenir quelle version est installée dans un projet.
On pourrait penser que la version du composant peut se trouver dans le fichier composer.json du composant, mais il n'en est rien (vérifier dans le fichier composer.json du composant symfony/mailer, aucune version de ce composant à l'horizon).
Ainsi, ça peut devenir compliqué, si je souhaite travailler en équipe, d'avoir les mêmes versions des packages entre différents collègues. De même, si un nouveau contributeur rejoint l'équipe, il serait long et pénible de lui envoyer tout le dossier vendor.
À la place, Composer crée automatiquement et maintient un fichier composer.lock, qui contient la version précise installée dans notre projet. Pour aller plus loin même, la version est en fait associée à un commit sur Github, pour retrouver la version précise du point de vue de Git.
Il est donc inutile d'inclure le dossier vendor au contrôle de version, puisqu'il pourra être reconstruit par Composer seulement avec le fichier composer.lock, beaucoup plus léger. Même s'il est long et complexe, nous n'avons pas à nous en occuper, puisque c'est Composer qui le gère.
Ce sont donc les fichiers composer.json et composer.lock qui seront versionnés, pas le dossier vendor
Installation et mises à jour de dépendances
Composer nous offre des commandes pour installer, mettre à jour et supprimer des dépendances.
composer install
Quand on récupère un projet existant, le dossier vendor n'existe pas, c'est normal. On ne trouvera que les fichiers composer.json et composer.lock.
On peut alors exécuter la commande suivante :
composer install
Avec composer install, Composer va directement voir le fichier composer.lock s'il existe. C'est ce qui lui permettra de rétablir précisément les versions que le développeur qui a committé a installées
composer require
Comme vu précédemment, permet d'ajouter une nouvelle dépendance au projet.
On peut bien sûr indiquer à Composer la contrainte de version qu'on souhaite :
composer require "symfony/mailer:6.1.*"
require et installAttention à la différence entre require (ajouter une nouvelle dépendance) et install (télécharger et installer les dépendances à l'aide des fichiers composer) !
composer update
Comme son nom l'indique, permettra de mettre à jour les dépendances, tout en respectant les intervalles de versions indiqués dans composer.json pour chaque dépendance.
On peut évidemment restreindre les packages qu'on souhaite mettre à jour, en ajoutant le nom derrière :
composer update symfony/mailer
Il existe un alias composer upgrade qui fait exactement la même chose
composer remove
Pour supprimer un package (renseigner son nom derrière remove bien sûr).
Les dépendances de développement
Dans un fichier composer.json on pourra égalemen trouver une section require-dev.
Cette section recense les dépendances de développement, c'est-à-dire tout ce dont nous aurons besoin lors de la phase de développement, mais pas en production par exemple (lorsque le site est déployé, en ligne).
Un exemple type est PHPUnit, qui est le framework de tests automatisés le plus utilisé dans l'écosystème PHP.
On aura typiquement besoin de PHPUnit lors de la phase de développement ou de tests, mais une fois le code en production, ce n'est plus le moment d'exécuter les tests. En production par exemple, pour installer les dépendances, mais sans les dépendances de développement, on exécutera la commande suivante :
composer install --no-dev
Et pour installer une dépendance de développement :
composer require --dev phpunit/phpunit # ou n'importe quelle autre dépendance
Les scripts
Les scripts servent à exécuter une commande au sein de notre projet, en passant par Composer.
Par exemple, on pourrait définir un script qui lance les tests PHPUnit, pour éviter d'avoir à réécrire la commande à chaque fois. On pourra alors nommer la commande puis l'exécuter en faisant composer [nom-du-script].
Par exemple, si je veux exécuter des tests dans un conteneur Docker :
composer.json
{
"scripts": {
"test-in-docker": "docker-compose exec php ./vendor/bin/simple-phpunit --testdox"
}
}
Puis, à la racine du projet :
composer test-in-docker
Autres
On pourra trouver d'autres sections dans un fichier composer.json :
suggest: suggère à l'utilisateur l'installation d'autres packages avec lesquels le sien a une excellente compatibilitéconfig: quelques éléments de configuration Composer, comme trier les packages par ordre alphabétique, autoriser des plugins tiers...extra: des informations personnalisés qui pourront être exploitées par des pluginsconflict: comme son nom l'indique, une liste de packages avec lesquels le package n'est pas compatible. L'installation sera alors refuséeautoload: les règles d'auto-chargement des classes au sein du projetautoload-dev: des règles d'auto-chargement supplémentaires à appliquer dans un environnement de tests par exemple