Dépendances, version et Composer
Objectifs : Réfléchir à la gestion des dépendances dans un projet, et résoudre le problème de la dépendance incontrôlée sur Composer. Thèmes : Développement, PHP, Dépendances, Composer, Déploiement.
Dependency manager
L'utilisation de dépendances dans une stack logicielle est soumise à une règle d'or :
Pour chaque dépendance, mettre en place un contrôle des versions autorisées, en fonction de la confiance accordée à son mainteneur.
Dans l'univers JavaScript, on utiliserait Yarn, qui permet via un package.json
de décrire un schéma des versions de dépendances acceptées.
Côté PHP, on a pris l'habitude d'utiliser Composer depuis quelques années pour gérer nos dépendances. Et ça donne :
{
"require": {
"monolog/monolog": "1.0.*"
}
}
Exemple tiré de la documentation officielle de Composer
Dans cet exemple, on intègre le package monolog/monolog
à notre projet, en explicitant la contrainte : version 1.0, ou tout patch sur cette version mineure.
Semantic versioning
Tout ça peut fonctionner car basé sur Semantic Versioning, une RFC qui définit un ensemble de règles pour la numérotation des versions (qui crée donc une cohésion sur la signification des numéros de version).
Dans l'exemple précédent, c'est Semantic Versioning qui nous permet de traduire la numérotation 1.0.* en version 1.0, ou tout patch sur cette version mineure.
Ensuite, c'est la confiance qu'on a envers les mainteneurs de la dépendance monolog/monolog
qui nous permet d'être souple et d'accepter tous les patchs qui pourraient être publiés.
Dans le cas où l'on intègre la dépendance d'un mainteneur auquel on accorde moins de confiance, on peut remplacer par "monolog/monolog": "1.0.8"
, afin de laisser la place au moins de risque possible (la version choisie ayant été testée directement lors des développements, et étant donc fonctionnelle à un moment T).
Verrouillage
Le dernier mécanisme — nécessaire — apporté par les Dependency Manager modernes est le mécanisme de verrouillage des dépendances. Il permet de verrouiller les versions des dépendances de manière stricte lors de phases de release (jusque là, l'application des contraintes de version se fait lors des phases de développement).
Dans le cas de Composer, ce mécanisme est implémenté via le fichier composer.lock
:
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2bdb78875a3c225905ed078c9d9140f6",
"packages": [
{
"name": "psr/cache",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
"reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
"shasum": ""
},
...
},
...
]
}
On voit que ce fichier (qui doit faire partie des sources à release) fixe la dépendance psr/cache
à la référence de commit précise d11b50
. On maîtrise donc entièrement quelle version de notre dépendance va être utilisée lors d'un déploiement.
Recommandation officielle
#!/bin/sh
EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
>&2 echo 'ERROR: Invalid installer checksum'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
Script d'installation automatique proposé par la documentation officielle de Composer
Composer propose un utilitaire d'installation, signé, sous la forme du script PHP composer-setup.php
. La documentation indique qu'on peut choisir la version de l'utilitaire à utiliser. Cependant, il n'est pas indiqué qu'on DEVRAIT choisir la version de Composer à installer, et comment faire.
Cordonnier mal chaussé
Fin 2018, Composer sortait sa version 1.7.3
(un patch sur une version mineur, donc). Le script d'installation ci-dessus étant massivement utilisé, fleurirent sur Twitter et Github une pluie de commentaires de type :
Yesterday composer install works fine. Without any changes to composer.json & composer.lock today it fails
https://github.com/composer/composer/issues/7764 https://github.com/composer/composer/issues/7974 https://github.com/composer/composer/issues/7813
Et des réponses des mainteneurs du type :
This isn't a bug, it was a bug before which was fixed.
Avec l'arrivée de Composer 2 dans les prochains mois, les scripts de déploiement en production des applications PHP risquent de se mettre à planter. On commence à voir apparaître dans les logs de déploiement des Deprecation Notices qui n'existaient pas quelques mois auparavant.
Être strict AUSSI avec Composer
Les mainteneurs de Composer ont fait un travail admirable sur la stabilité du projet, et ont permis que puisse être utilisée quasiment n'importe quelle version de l'outil sans causer de problème. La confiance s'est donc installée, et la sécurité énormément relâchée. Afin de passer correctement le cap des prochains mois, il sera sans doute judicieux d'être à nouveau strict avec le Composer souhaité sur chaque projet.
Le script d'installation automatique pour Linux propose (voir les sources sur Github, bien que cela ne semble pas documenté) plusieurs options, dont --version
qui nous intéresse particulièrement :
Composer Installer
------------------
Options
--help this help
--check for checking environment only
--force forces the installation
--ansi force ANSI color output
--no-ansi disable ANSI color output
--quiet do not output unimportant messages
--install-dir="..." accepts a target installation directory
--preview install the latest version from the preview (alpha/beta/rc) channel instead of stable
--snapshot install the latest version from the snapshot (dev builds) channel instead of stable
--version="..." accepts a specific version to install instead of the latest
--filename="..." accepts a target filename (default: composer.phar)
--disable-tls disable SSL/TLS security for file downloads
--cafile="..." accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification
L'option --version
semble accepter en paramètre n'importe quel tag disponible au niveau du dépôt git Composer. Pour installer une version précise de Composer, on peut donc remplacer dans notre script :
php composer-setup.php --quiet --version="1.10.6"
Il ne semble malheureusement pas y avoir de possibilité d'utiliser une contrainte plus souple de version (de type 1.10.*
ou ^1.10
) à ce jour.