Phar…midable !

Si vous êtes un tant soit peu familier avec l’écosystème PHP, vous utilisez probablement Composer pour gérer les dépendances de vos applications. Il y a alors des chances de trouver PHP CS Fixer ou encore PHPUnit dans vos composer.json. Mais qu’est-ce qu’une dépendance au juste ?

Bibliothèque ou programme tiers utilisé par le programme courant.

Quel que soit son point d’entrée, aucune de vos applications n’utilisera ni PHP CS Fixer, ni PHPUnit. Ces derniers ne sont donc pas des dépendances, juste des programmes tiers. Lorsqu’un tel programme est codé en PHP, il est généralement disponible sous forme de PHAR.

Un fichier .phar — acronyme de PHP archive — a pour particularité d’être un exécutable contenant son propre code source, ce qui apporte d’emblée quelques avantages :

Ce dernier point rend leur gestion particulièrement adaptée à un Makefile. Prenons PHP CS Fixer par exemple. Si vous êtes pressé, vous pouvez le trouver plus bas. Sinon, suivez le guide !

Créer un dossier commun

tools:
	mkdir $@

Nous avons ici une règle dont tools est la cible, suivie d’une commande. On utilise make (ou nmake sous Windows) pour exécuter les commandes d’une règle en lui passant sa cible en argument :

$ make tools
mkdir tools

$@ est une variable automatique dont la valeur est la cible courante. Comme les commandes d’une règle ne sont exécutées que si sa cible n’existe pas, make tools créera un dossier tools seulement si ce dernier n’existe pas. Sinon, vous obtiendrez simplement

make: « tools » est à jour.

Y placer nos PHARs permettra de ne pas les versionner simplement en ignorant le dossier, et le cas échéant, de les mutualiser entre les applications du projet.

Installer le PHAR

tools/php-cs-fixer-%.phar: tools
	rm -f tools/php-cs-fixer-*.phar
	curl -LSso $@ \
		"https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v$*/php-cs-fixer.phar"

tools/php-cs-fixer-%.phar est une cible un peu spéciale (une pattern rule) car son caractère % pourra être remplacé par n’importe quelle chaîne de caractères non vide. Cette dernière est alors accessible grâce à la variable automatique $*.

Pour placer le PHAR dans le dossier tools, nous avons besoin que ce dernier existe. C’est pourquoi on le retrouve à droite de la cible, comme composant : la cible d’une règle qui devra être exécutée avant les commandes.

$ make tools/php-cs-fixer-2.17.3.phar
mkdir tools
rm -f tools/php-cs-fixer-*.phar
curl -LsSo tools/php-cs-fixer-2.17.3.phar \
		"https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.17.3/php-cs-fixer.phar"

Ces dernières sont relativement simples :

  1. On supprime toute autre version de PHP CS Fixer éventuellement présente dans le dossier tools.
  2. On télécharge la version voulue dans ce même dossier.

Rappelez-vous que ces commandes ne seront pas exécutées si la cible existe. Ainsi la règle nous assure que seule la version voulue sera à disposition, en la téléchargeant si nécessaire.

Notez que j’utilise ici une URL de la section Releases de GitHub et pas https://cs.symfony.com/download/php-cs-fixer-v2.phar car cette dernière ne permet pas de savoir quelle version sera téléchargée. Or, il est vivement conseillé que tous les acteurs du projet utilisent la même à un instant t.

Avant de passer à la suite, petit point sur les options de curl :

Notez enfin que la règle tools pourrait être remplacée par l’option --create-dirs de curl. À vous de voir !

Utiliser le PHAR

.PHONY: php-cs-fixer
php-cs-fixer: tools/php-cs-fixer-2.17.3.phar
	php $< $(arguments)

php-cs-fixer est également une cible spéciale car ce n’est pas un fichier à créer, juste une action à exécuter. Il est conseillé de déclarer une telle cible « phony » en l’ajoutant comme composant de .PHONY. La règle sera alors toujours exécutée, car make ne vérifiera jamais l’existence d’un fichier php-cs-fixer. Pour la petite histoire, phony se traduit par « faux », ou « contrefait » en français. Nous déclarons donc que notre cible n’en est pas réellement une !

Pour utiliser le PHAR, nous en avons bien entendu besoin et ça tombe bien, il s’agit de la cible précédente. Nous l’ajoutons donc comme composant en lui spécifiant la version voulue. De manière opportune, il devient alors la valeur de la variable automatique $<.

Reste à passer d’éventuels arguments au PHAR : si un argument de make est une assignation, la variable concernée sera accessible au sein du Makefile avec la valeur spécifiée. Ici nous faisons référence à une variable arguments, et pouvons donc écrire

$ make php-cs-fixer arguments=-V
php tools/php-cs-fixer-2.17.3.phar -V
PHP CS Fixer 2.17.3 Desert Beast by Fabien Potencier and Dariusz Ruminski (bd32f5d)

Nous avons maintenant la liberté de passer n’importe quels arguments à PHP CS Fixer, mais au prix d’une syntaxe assez contraignante. La plupart du temps, le nombre de cas d’utilisation est très limité. Par exemple ici, nous aurons principalement besoin de corriger le style de programmation, ou de vérifier qu’il adhère à nos standards. Pour cela, nous pouvons créer des règles spécifiques qui simplifieront l’utilisation du PHAR :

.PHONY: fix-php-cs
fix-php-cs:
	$(MAKE) php-cs-fixer arguments="fix ."

.PHONY: check-php-cs
check-php-cs:
	$(MAKE) php-cs-fixer arguments="fix . --stop-on-violation --dry-run --quiet"

Pour une fois, aucune cible n’est spéciale…! Ces règles se contentent d’exécuter php-cs-fixer avec les arguments voulus. Pour ce faire, on utilise la variable MAKE qui contient le chemin de la version de make utilisée. On parle alors de sub-make.

Notez que l’exécution d’une de ces règles provoquerait beaucoup d’affichage inutile : chaque commande exécutée, plus un message d’entrée et sortie du sub-make. On s’en préviendra grâce à l’option -s.

$ make -s fix-php-cs
Loaded config default.
Using cache file ".php_cs.cache".

Fixed all files in 0.160 seconds, 12.000 MB memory used

Le Makefile complet

tools:
	mkdir $@

tools/php-cs-fixer-%.phar: tools
	rm -f tools/php-cs-fixer-*.phar
	curl -LSso $@ \
		"https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v$*/php-cs-fixer.phar"

.PHONY: php-cs-fixer
php-cs-fixer: tools/php-cs-fixer-2.17.3.phar
	php $< $(arguments)

.PHONY: fix-php-cs
fix-php-cs:
	$(MAKE) php-cs-fixer arguments="fix ."

.PHONY: check-php-cs
check-php-cs:
	$(MAKE) php-cs-fixer arguments="fix . --stop-on-violation --dry-run --quiet"

Un simple make fix-php-cs et

Ajoutons à cela qu’un PHAR local au projet

Ce dernier point signifie que vous pouvez profiter de l’autocomplétion, en éditant .php_cs.dist par exemple.

Bonus : PHPUnit

Nous venons de gérer PHP CS Fixer avec brio, mais notre Makefile est facilement extensible ; il serait dommage de s’arrêter là !

tools/phpunit-%.phar: tools
	rm -f tools/phpunit-*.phar
	curl -LSso $@ \
		"https://phar.phpunit.de/phpunit-$*.phar"

.PHONY: phpunit
phpunit: tools/phpunit-9.5.0.phar
	php $< $(arguments)

.PHONY: unit-test-php
unit-test-php:
	$(MAKE) phpunit arguments="."

On change l’URL du PHAR, sa cible, et on crée les règles appropriées. It just works! À vous d’ajouter vos propres outils maintenant !