Bonnes pratiques pour commencer avec Composer

Qu’est-ce que Composer ?

Composer est un gestionnaire de dépendances pour PHP, similaire à NPM ou Yarn pour Javascript et pip pour Python. Il simplifie la gestion des paquets dont dépend votre application. En tant que « service » supplémentaire, Composer offre la possibilité aux paquets d’enregistrer un autochargeur. Et cette combinaison fait de Composer l’un des utilitaires les plus critiques de l’écosystème PHP à mon avis.

Le dépôt de paquets le plus utilisé par Composer est Packagist.org. Ce dépôt de paquets n’accepte que les paquets open source. Si vous souhaitez bénéficier de toutes les fonctionnalités offertes par Composer, vous pouvez utiliser son offre privé.. Une version payante de Packagist, construite par les créateurs de Composer, qui permet d’utiliser des paquets à code source fermé avec un contrôle d’accès granulaire.

Je ne vais pas expliquer ici comment installer et configurer Composer. Les mainteneurs de Composer l’expliquent en détail dans la page d’accueil de Composer. Introduction à composer sur son site. Pour tous les exemples où j’utilise la CLI de Composer, je supposerai que Composer est installé globalement et est appelé via composer.

Séparation des dépendances régulières et de développement

Composer fait une distinction entre deux types de dépendances : les dépendances régulières et les dépendances de développement. Les dépendances régulières sont les dépendances que votre code a toujours besoins, indépendamment de l’environnement dans lequel le code est exécuté. Les dépendances de développement sont des dépendances qui ne sont nécessaires que lors du développement du code, par exemple les frameworks de test comme PHPUnit, les analyseurs de code statique comme Psalm et les analyseurs de style de code comme PHP Code Sniffer. Elles ne sont pas nécessaires pour exécuter le code dans un environnement de production. Lorsque vous ajoutez une dépendance via composer require [package] elle sera marquée comme une « dépendance régulières ». En utilisant l’indicateur --dev le paquet sera marqué comme une « dépendance de développement ».

Lors de l’exécution de composer install les dépendances de production et de développement de votre application sont téléchargées et installées. Afin de télécharger et d’installer uniquement les dépendances de production, ajoutez l’indicateur --no-dev. C’est généralement ce que vous voulez dans votre pipeline de déploiement car vous n’avez pas besoin des dépendances de développement à cet endroit.

NB composer install téléchargera et installera uniquement les dépendances de développement de votre application ou paquet et non les dépendances de développement des dépendances. Ainsi, si vous avez une dépendance sur « Package A » et que « Package A » a une dépendance de développement sur « Package B », seul « Package A » sera téléchargé et installé dans votre projet.

Les dépendances de développement peuvent s’accumuler jusqu’à une liste raisonnable de dépendances et par conséquent une bonne quantité de fichiers. Tous ces fichiers sont autochargeables via l’autochargeur. Cela permet d’économiser de l’espace de stockage, du temps de transfert (pendant le déploiement) et du temps d’exécution pour minimiser la quantité de dépendances de production dans votre application. Les économies d’espace de stockage et de temps de transfert peuvent être négligeables à petite échelle, mais dès que la taille de votre application ou le nombre de ses responsables augmente, ces économies peuvent être plus importantes. Un autre avantage du marquage des dépendances de développement est qu’elles ne seront pas incluses dans les constructions de Composer autoloader lors de l’exécution de composer install --no-dev.

Chargement automatique ( autoload.php )

Comme mentionné précédemment, une autre caractéristique de Composer est qu’il offre des utilitaires permettant aux paquets de greffer à votre projet PHP avec le chargement automatique ( autoload.php ). Avec cette fonctionnalité, les paquets peuvent spécifier comment Composer peut faire correspondre un nom de classe avec un nom de fichier. Vous n’avez plus plus besoin de remplir votre code d’appels require_once et require. Tout ce que vous avez à faire est d’inclure vendor/autoload.php.

Comme pour les dépendances, Composer propose deux types d’autoloaders : les directives autoload ordinaires et les directives autoload de développement. Les paquets peuvent utiliser cette fonction pour réduire la taille de leur paquet et de leur autoloader, mais les applications peuvent également en bénéficier. Dans le cas d’un projet bien testé, la quantité de fichiers utilisés uniquement pour les tests peut représenter une part importante du code de base. Comme pour tous les utilitaires de développement tels que les outils de ligne de commande. Vous n’en avez pas besoin en production. En indiquant à l’autoloader que ce sont des fichiers pour un autoloader de développement, ils ne seront pas pris en compte lors de l’exécution de la commande composer install --no-dev ce qui peut améliorer les performances de votre application. La distinction entre l’autoload régulier et l’autoload de développement est faite dans le fichier de configuration composer.json :

{
    "autoload": {
        "psr-4": {
            "MyApp\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "MyApp\Tests": "tests/"
        }
    }
}

Composer offre des outils supplémentaires pour améliorer les performances des fonctions et des classes à chargement automatique via les éléments suivants optimisation de l’autoloader.

Contraintes de version

Avant Composer, un développeur téléchargeait une version spécifique d’une dépendance et la plaçait dans le contrôle de version. Depuis Composer, nous pouvons nous éloigner d’une version spécifique d’une dépendance et laisser Composer décider exactement de la version d’une dépendance à installer. Pour déterminer cela, Composer s’appuie fortement sur le concept de versionnement sémantique. Composer ne fait pas seulement cela pour votre application, mais étend cette fonctionnalité aux dépendances. Cela permet aux paquets d’avoir leurs propres dépendances, qui peuvent avoir leurs propres dépendances et ainsi de suite. Cela conduit à des paquets plus nombreux mais plus petits. Ces petits paquets peuvent se concentrer sur une seule fonctionnalité, ce qui permet de centraliser cette fonctionnalité.

Par rapport à NPM par exemple, Composer n’autorise qu’une seule version d’un paquet par application. Cela signifie que moins de paquets doivent être installés, mais cela pose un risque supplémentaire de collisions de versions. C’est une bonne raison pour ne pas utiliser une version spécifique d’une dépendance, mais demander une gamme de versions. Cela se fait généralement par l’intermédiaire de l’opérateur caret : ^1.0. Cela signifie que chaque version >= 1.0.0 et < ; 2.0.0 est accepté. Étant donné que c’est du versionnage sémantique, cela signifie que vous êtes assuré de disposer des fonctionnalités requises et qu’aucun changement de compatibilité ascendante n’est introduit.

En spécifiant une plage au lieu d’une version spécifique, vous évitez les collisions de versions lorsqu’une autre dépendance utilise le même paquet. Supposons la situation suivante :

Le site composer.json de votre application :

{
  "require": {
    "vendor/package": "^1.4",
    "another_vendor/package": "^1.0"
  }
}

Le composer.json de vendor/package:

{
  "require": {
    "another_vendor/package": "^1.2"
  }
}

Lors de l’installation des dépendances, Composer détectera que des demandes multiples de another_vendor/package sont présentes, mais comme il s’agit de contraintes, Composer peut déterminer qu’il devrait éviter d’installer la version 1.1.5 mais devrait plutôt installer 1.2.8.

Composeur a plusieurs opérateurs de contrainte et permet même de combiner des opérateurs. Cela signifie qu’une contrainte comme « php » : « ^7.2 » n’inclura pas PHP 8. Vous pouvez indiquer que PHP 7.2 et PHP 8 sont supportés, deux opérateurs peuvent être combinés : « php » : « ^7.2 || ^8.0 ».

NB Une autre option serait d’adopter l’approche Symfony a pris et opté pour "php": >= 7.2". Personnellement, je ne suis pas favorable à cette approche car elle indique que le code fonctionnera également avec PHP 9 et plus. Comme il n’y a aucun moyen de savoir quelles modifications de rétrocompatibilité seront introduites dans PHP 9, je préfère ne pas faire de telles promesses et c’est la raison pour laquelle j’ai prévu d’utiliser "php": "^7.2 || ^8.0" dans mes dépôts.

Root ou pas Root

Après l’analyse d’un composer.json une distinction peut être faite entre les dépendances requises directement à la racine à partir de votre projet et celles qui ne le sont pas. Les packages Root Package dans la terminologie de Composer sont les dépendances à la racine qui ont recours à leurs propres dépendances.

Prenons ce composer.json :

{
    "require": {
        "laminas/laminas-diactoros": "^2.0"
    }
}

Exécution la commande composer install sur ce fichier montrerait que non seulement que laminas/laminas-diactoros est téléchargé, mais aussi laminas/laminas-zendframework-bridge, psr/http-factory et psr-http-message. Dans cet exemple laminas/laminas-diactoros est une Package Root fondamentale dans notre projet.

La différence entre un packet à la racine et une packet dérivée réside dans la quantité de dépendances directes dans votre projet. En général, moins de packets à la racines ( package root ) signifie moins de paquets et moins de conflits lors de la mise à jour des paquets. Moins de paquets parce qu’une nouvelle version d’un paquet peut avoir des dépendances différentes, qui seront automatiquement gérées par Composer. Et moins de conflits parce qu’il y a moins de contraintes dans votre arbre de dépendances.

Mais qu’est-ce qui rend un paquet éligible pour être à la racine ?

{
    "require": {
        "laminas/laminas-diactoros": "^2.0"
    }
}

Si l’on regarde l’exemple où l’on s’appuie uniquement sur laminas/laminas-diactoros. diactoros offre une implémentation des interfaces spécifiées dans psr-http-message et psr/http-factory. Si dans mon code je ne fais référence qu’à des objets de l’interface laminas/laminas-diactoros ce serait une configuration Composer valide. Mais si je me réfère aux interfaces définies dans psr-http-message et psr/http-factory ce qui serait une bonne pratique à mon avis – alors ce serait une raison pour faire de psr-http-message et psr/http-factory un package à la racine de notre projet ( Package root ).

{
    "require": {
        "laminas/laminas-diactoros": "^2.0",
        "psr-http-message": "^1.0",
        "psr/http-factory": "^1.0"
    }
}

Spécifier votre environnement

Vous construisez votre application ou votre paquetage sur une version donnée de PHP. Et vous ne pouvez garantir le bon fonctionnement de votre code que sur cette version de PHP. Peut-être votre code nécessite-t-il l’installation d’une extension PHP. C’est pourquoi c’est une bonne pratique de préciser quelle(s) version(s) de PHP sont requises ou supportées et quelles extensions sont nécessaires.

Lorsque vous développez un paquetage, les versions de PHP et les extensions PHP peuvent être utilisées comme s’il s’agissait de paquets ordinaires :

{
  "require": {
    "php": "^7.2",
    "ext-curl": "*"
  }
}

La raison pour laquelle nous pouvons utiliser le joker * pour l’extension cURL est que l’extension est liée à PHP et qu’il n’est donc pas nécessaire de spécifier la version.

Vous pouvez également utiliser cette approche lorsque vous construisez une application, en plus de spécifier ces restrictions comme configuration de la plate-forme :

{
  "config": {
    "platform": {
      "php": "7.4.4",
      "ext-curl": "*"
    }
  }
}

L’avantage de cette méthode est que Composer utilisera la version 7.4.4 de PHP comme version de PHP, même si une version différente est installée sur le système où les commandes de Composer sont exécutées. Avec la tendance à utiliser des solutions de virtualisation comme Docker et Vagrant, cela permet aux utilisateurs d’exécuter les commandes de Composer localement et d’exécuter l’application dans l’environnement virtualisé. Cela permet également d’éviter qu’un collègue qui utilise une version PHP de pointe installe des versions de dépendances qui ne sont pas prises en charge par l’environnement de production.

Travailler avec un logiciel de gestion de version ( Git, Mercurial, Subversion )

Dans votre projet, Composer aura trois entités sur votre système de fichiers ; deux fichiers appelés composer.json et composer.lock et un dossier appelé vendor*. Mais il n’est pas nécessaire que ces trois éléments soient inclus dans votre gestion de version.

Le fichier composer.json contient tous les paquets qui sont requis avec des contraintes de version – voir la section « Contraintes de version » pour plus d’informations. Ce fichier doit toujours être ajouté au contrôle de version. Sans ce fichier, un utilisateur qui souhaite utiliser votre code n’a aucune idée des paquets nécessaires.

Le fichier composer.lock contient les versions exactes, y compris les sommes de contrôle (checksum), de tout les paquets. Si ce fichier est présent lors de l’exécution de composer install il installera exactement les mêmes versions que celles spécifiées dans le fichier composer.lock. Cette fonctionnalité vous permet de vous assurer que les dépendances de tous les environnements sont les mêmes. Lors de la création d’une application, il est souhaitable d’ajouter ce fichier au contrôle de version. La raison principale est que les applications (ou autres paquets) qui utilisent votre paquet ne prendront pas ce fichier en compte, mais votre environnement de développement (local) le fera. Cela peut vous conduire à développer ou à tester avec des versions obsolètes de vos dépendances, alors que les utilisateurs de votre paquet utilisent les dernières versions.

Le dossier vendor contient le code des paquets. Le contenu de ce dossier ne doit pas être ajouté au contrôle de la source. Dans le cas ou vous utilisez Git comme gestion des versions, il faudra l’ajouter dans un .gitignore pour ne pas le versionner.

* Vous pouvez changer le nom et l’emplacement de ce dossier via l’option de configuration vendor-dir mais pour cet article, j’ai choisi la valeur par défaut.

Modification des dépendances

Supposons que vous souhaitiez mettre à jour une dépendance de votre application. Ou peut-être voulez-vous en mettre plusieurs à jour. Vous exécutez un certain nombre de composer update et vous voyez composer.lock changer. Vous créez un commit. Un autre lot de mises à jour et un autre commit. Quelques jours plus tard, vous reprenez là où vous vous étiez arrêté. Vous travaillez avec un dépôt très actif, donc avant de continuer, vous rebasez pour vous assurer que vos changements soit compatibles avec la dernière version du code. Conflit de merge ! Un conflit de merge dans composer.json peut être résolu comme tout autre conflit de merge, mais un conflit de composer.lock est plus difficile à résoudre.

La façon de résoudre les conflits de merge dans un composer.lock est d’accepter les changements entrants – ce sont les derniers changements – et d’exécuter à nouveau les commandes Composer que vous avez exécutées pour effectuer les changements en premier lieu. Cela garantira que vos modifications seront appliquées à la version la plus récente de votre code. Validez les changements de composer.lock et continuez le rebasement. Si vous avez effectué d’autres travaux sur Composer dans un prochain commit, vous devrez repasser par ces étapes.

Conclusion

Composer est devenu une partie essentielle du développement de PHP. Comme tout gestionnaire de paquets, il peut avoir besoin d’optimisations. J’utilise Composer quotidiennement dans plusieurs dépôts, au cours de cette période, j’ai utilisé certaines pratiques que je considère comme de bonnes pratiques de :

  • Divisez vos dépendances en dépendances de production et de développement pour réduire la taille de l’application.
  • Divisez votre autoloader en autoloader de production et de développement pour diminuer la taille de l’autoloader et augmenter les performances.
  • Ne dépendez pas d’une version fixe des dépendances, mais plutôt d’une gamme pour rendre la mise à niveau des paquets moins pénible.
  • Ne dépendez directement que des dépendances que vous appelez réellement dans votre application afin de simplifier la mise à niveau ou le passage à une alternative.
  • Toujours mettre composer.json dans le contrôle de la source, mais seulement composer.lock dans le contrôle de source pour les applications afin d’éviter que les utilisateurs de votre paquet ne puissent pas mettre à jour d’autres paquets.
  • Essayez d’avoir un maximum d’un commit par pull request/merge request pour contenir les modifications de composer.json et composer.lock pour minimiser les conflits de fusion dans ces fichiers.
Nouveau Tutoriel

Newsletter

Ne manquez jamais les nouveaux conseils, tutoriels et autres.

Pas de spam, jamais. Nous ne partagerons jamais votre adresse électronique et vous pouvez vous désabonner à tout moment.