Paramètres nommés en PHP8

Divulgâchage : Lorsqu’on développe un programme, on appelle parfois des fonctions (ou des méthodes) qui ont de très nombreux paramètres. C’est souvent gênant, parfois dangereux. Pour contourner le problème, nommer ses paramètres lors de l’appel est maintenant possible nativement en PHP 8.

Pour développer un programme informatique, les développeurs passent une partie de leur temps à utiliser des librairies de fonctions déjà prêtes sensées leur simplifier la vie. Qu’importe le sujet, il y a souvent quelqu’un quelque part qui l’a déjà rencontré et avec de la chance, il a publié sa solution que vous pouvez utiliser.

Et lorsqu’on utilise une librairie externe, on est donc bien obligés de s’y adapter. La plupart du temps, les choses se passent bien mais, parfois, la librairie n’est pas si bien écrite que ça. C’était sûrement une bonne idée à l’époque mais depuis, on a trouvé d’autres manières de faire plus pratique et lisible.

Bien étiqueter. contact357 @ pixabay

Aujourd’hui, je voudrais aborder l’appel aux fonction qui gèrent un grand nombre de paramètres et/ou des paramètres avec valeurs par défaut. C’est très pratique de disposer d’un fonction qui fait tout et avoir des valeurs par défaut permet une version de base utilisable, mais lorsqu’on les appelles, le code n’est pas toujours lisible.

On pourrait vouloir corriger le code (il y a tout un tas de technique pour réduire ce nombre de paramètres) mais, bien souvent, ça n’est tout simplement pas possible et on doit faire avec. Alors comment rendre notre code lisible si celui de base est un peu tout pourri ?

Il y a deux ans et demi, j’avais publié une technique pour pouvoir bénéficier de paramètres nommés en PHP 5 et 7. Un peu comme un cadeau de noël avancé, c’est maintenant disponible nativement en PHP8.

Exemple de méthode

N’importe quelle fonction ou méthode qui a beaucoup de paramètres posera le même genre de problème. C’est d’autant plus vrai si certains paramètres sont facultatifs.

Alors, uniquement pour le besoin de la démonstration, admettons que vous ayez décidé d’adapter la librairie GD à une programmation orientée objet dans l’idée, fort louable, d’améliorer les choses…

class Image {
    
    public function __construct($width, $height) {
        $this->gdImage = imagecreatetruecolor ($width, $height) ;
    }
    
    public function __destruct() {
        imagedestroy($this->gdimage) ;
    }
    
    public function png() {
        header("Content-type: image/png");
        return imagepng($this->gdImage) ;
    }
    
    function color($r, $g, $b) {
        return imagecolorallocate ($this->gdImage, $r, $g, $b) ;
    }
    
    // ...
}

Cette classe dispose de tout ce qu’il faut pour créer une image, la tracer et récupérer des couleurs. Adaptons maintenant la fonction imagearc() de GD. Pour bien faire, j’ai ajouté quelques valeurs par défaut… Si height reste à null, ça sera un cercle. start et end font tout le tour par défaut. J’ai aussi ajouté un paramètre pour pouvoir passer les angles en radian.

function draw_arc(
    $cx,$cy, $color, $width,
    $height = null,
    $start = 0,
    $end = 360,
    $isradiant = false
) {
    if ($isradiant) {
        $start = $start * 180 / pi() ;
        $end   = $end   * 180 / pi() ;
    }
    imagearc($this->gdImage,
             $cx, $cy, $width,
             $height ?: $width,
             $start, $end, $color) ;
}

Du coup, j’ai du avancer la couleur dans la liste (parce que gérer une valeur par défaut aurait compliqué l’exemple). C’en est d’autant moins pratique mais c’est un peu le but de ce genre d’exemple, vous montrer comment faire lorsque le code de base n’est pas ce qu’il y a de plus clair.

Quel est le problème ?

Les premières méthodes de la classe sont assez simple, comme créer une image et une couleur :

$i = new Image(320, 240) ;
$c = $i->color(255, 255, 255) ;

Le problème du jour se concrétisera lorsqu’on appelera draw_arc car les appels ne seront pas toujours très lisibles. Si on n’a pas la signature de la méthode en tête, on peut avoir du mal à saisir quels chiffres servent à quoi…

$i->draw_arc(150, 150, $c, 100, 100, $s, $e, true) ;
$i->png() ;

Quelle est la distance entre le cercle et le coin supérieur gauche ? 150,150 ou 100,100 ? Réciproquement, quel est son rayon (50, 100, 200) ? Les angles $s et $e sont en radian ou en degrés ?

Résultat du premier exemple
$i->draw_arc(150, 150, $c, 100) ;
$i->png() ;

Le paramètre $c… C’est une couleur ou la largeur de la forme (comme c’est le cas dans la fonction originale) ?

Résultat du second exemple

Face au doute, on va lire la documentation si elle a été écrite ou le code sinon. Et lorsque le code contient beaucoup d’appels, on passe beaucoup de temps à faire l’aller et retour. Bien sûr, les IDE peuvent nous faciliter la vie en nous montrant les signatures et la documentation des fonctions dans des encarts mais n’empêche qu’on y perd quand même du temps.

Bien sûr, avec l’expérience et l’habitude, les fonctions les plus courantes finissent par rentrer, mais c’est, à mon sens, un bon gaspillage de ressources cérébrales qui seraient bien mieux utilisées à autre chose.

Changer la classe ? mauvaise idée

On peut bien sûr partir de l’idée que, effectivement, tout ça n’arriverait pas si on l’avait développée autrement car on a effectivement des techniques pour réduire le nombre de paramètres et rendre les appels plus lisibles.

C’est vrai, si vous construisez un librairie, une classe ou même une fonction, y penser dès le début, c’est mieux. Mais une grosse partie du travail d’un développeur est de corriger et d’adapter l’existant et dans ce cas, les choses sont différentes.

C’est un vrai chantier. Aapo Haapanen @ flickr

Imaginez donc qu’un collègue zélé décide de modifier la classe pour améliorer tout ça. En fait, qu’importe comment il l’améliorera et qu’importe même si son résultat est effectivement meilleurs ou pas. Car ça va poser deux problèmes conséquents :

  1. Lors de la migration, il va devoir repasser sur tous les anciens appels pour les mettre à jours. C’est une tâche difficile car il faut garder en tête, en simultané, deux version de la même chose et donc rester très concentré pendant tout ce temps (le cerveau n’aime pas et il a vite fait d’inverser, confondre, mélanger…). Qu’il fasse la moindre petite erreur (et il en fera forcément plusieurs) et c’est un bogue qu’il faudra ensuite corriger. Immédiatement si on a la chance que cette portion soit couverte par un test automatique, ou bien plus tard lorsque le support aura un appel d’un client pas très content.
  2. Et ensuite, une fois la migration terminée, les anciens développeurs qui avaient leurs habitudes vont devoir s’adapter à la nouvelle version. Non seulement ils vont consacrer beaucoup de temps à lire la documentation pour s’y familiariser mais qu’un petit moment d’inadvertance survienne et leur cerveau peut confondre et mélanger la nouvelle et l’ancienne version et vous vous retrouvez de nouveau avec un bogue de plus…

Attention. Je ne dis pas qu’il faut éviter ce genre de nettoyage. Je dis simplement qu’ils ne sont pas faciles et nécessitent du temps et des efforts particuliers. Ressources rares et dont on ne dispose pas toujours lorsque d’autres chantiers, plus importants pour le projet, sont en cours.

Et sans même aller jusqu’au nettoyage de bonne intention, on fini toujours par avoir besoin de changer quelque chose et pour peu qu’on doive changer l’ordre des paramètres, lorsque l’un deux acquiert une valeur par défaut, ou un autre la perd, c’est toute la base de code qui doit être revue.

Les paramètres nommés sont là pour ça

Ce genre de chose est disponible depuis longtemps dans d’autre langages de programmation (comme python) mais après y avoir pensé depuis au moins 2013, le PHP vient de s’en doter dans sa version 8 🎉.

L’idée de base est très simple. Lorsque vous appelez une fonction et lui passez des paramètres, vous pouvez maintenant le faire de deux manières complémentaires :

  1. À l’ancienne, en fournissant les valeurs, dans l’ordre en vous arrêtant lorsque ceux qui restent on des valeurs par défaut qui vous satisfont.
  2. C’est nouveau, en les nommant. Vous énumérez les paramètres dont vous avez besoin par leur nom et leur associez la valeur.

Revenons sur notre exemple, le premier appel peut maintenant s’écrire plus lisiblement de la sorte. C’est plus long, mais on sait tout de suite qui sert à quoi (Même si ce cas particulier n’est pas très utile).

$i->draw_arc(
    cx: 200,
    cy: 200,
    color: $c,
    width: 100,
    height: 100,
    start: $s,
    end: $e,
    isRadian: true
) ;

L’intérêt est surtout lorsque vous ne devez spécifier que certains paramètres facultatifs, vous n’êtes plus obligé de les lister tous, vous pouvez ne renseigner que ceux dont vous avez vraiment besoin.

Ici, je ne mentionne plus height puisque je voulais un cercle :

$i->draw_arc(
    cx: 200,
    cy: 200,
    color: $c,
    width: 100,
    start: $s,
    end: $e,
    isRadian: true
) ;

Pour éviter les confusions, ou pour respecter des habitudes d’ordre, on peut également lister les paramètres dans l’ordre qu’on veut (puisqu’ils ont des noms).

Cette fois, je met la couleur à la fin :

$i->draw_arc( 
    cx: 200,
    cy: 200,
    width: 100,
    color: $c
) ;

Et lorsque les premiers sont dans l’ordre attendu, on peut aussi éviter de les nommer :

$i->draw_arc(
    200, 200,
    width: 100,
    color: $c) ;

En nommant vos paramètres, vous pouvez donc omettre ceux pour lesquels la valeur par défaut vous satisfait déjà, éviter les problèmes lorsque l’ordre n’est plus si évident et de manière générale, rendre votre appel plus lisible pour les autres qui le liront dans quelques temps.

Et après ?

Pour les curieux et les pressés qui ne veulent pas installer PHP8 sur leur système, vous pouvez aussi tester tout ça sur des interpréteurs en ligne (i.e. extendsclass.com).

Petite publicité pour les copains… Site de Cyril Bois, développeur français qui met à disposition gratuitement plein d’outils en ligne pour aider les développeur. Au 3 décembre 2020, c’était une des rares plateforme à proposer PHP8.

Mais quand je vois le nombre de projets qui sont encore en PHP 5, j’imagine que la plupart des développeurs devront attendre encore longtemps avant de pouvoir nommer leurs paramètres nativement…

Heureusement, les arsouyes ont pensé à vous et ont un article qui vous montre que même si ça n’est pas natif, vous pouvez quand même le faire en PHP 5 et 7 🎉.