Dans tous les langages de programmation qui possèdent des fonctions (c'est-à-dire tous), les fonctions peuvent être appelées en leur passant une liste ordonnée d'arguments en tant que paramètres d'entrée. Cette méthode fonctionne assez bien, mais elle n'est pas idéale dans certains cas. Plus précisément, lorsqu'il y a plus d'un paramètre, il est facile d'oublier leur ordre ou la signification d'un paramètre donné. (Et même lorsqu'il n'y en a qu'un, il peut ne pas être évident, au moment de l'appel, ce qu'il signifie.).
Il existe plusieurs solutions de contournement, comme le passage d'un tableau associatif au lieu de paramètres discrets, mais toutes introduisent leurs propres problèmes. Le passage d'un tableau associatif, par exemple, contourne toute sécurité de type.
Quelques langages résolvent ce problème en permettant (généralement de manière optionnelle) aux appelants de spécifier les paramètres par leur nom, plutôt que par leur position. PHP 8.0 est l'un de ces langages.
En PHP, les paramètres nommés sont entièrement contrôlés par l'appelant. Toutes les fonctions et méthodes supportent automatiquement les paramètres nommés. Lors de l'appel de la fonction, l'appelant peut choisir d'utiliser un argument positionnel, un argument nommé, ou même une combinaison des deux..
À titre d'exemple, la fonction array_fill()
de PHP produit un nouveau tableau d'une taille spécifiée où tous les éléments ont la même valeur de départ. Voici comment procéder :
<?php
$new = array_fill(0, 100, 50);
Rien qu'en lisant cela… que signifient ces nombres ? $new
sera-t-il un tableau de 100 éléments de 50
ou un tableau de 50 éléments de 100
? Ce n'est pas évident à moins de savoir comment fonctionne array_fill()
.
Avec les paramètres nommés, vous pouvez l'appeler comme ceci à la place :
<?php
array_fill(start_index: 0, count: 100, value: 50);
Maintenant, il est clair ce que nous obtiendrons en retour : 100 éléments de tableau, avec des clés commençant à 0, et dont toutes les valeurs sont fixées à 50
. Mais vous pouvez également ordonner les paramètres comme vous le souhaitez :
<?php
array_fill(
value: 50,
count: 100,
start_index: 0,
);
Les noms des arguments correspondent aux noms des paramètres dans la définition de la fonction, quel que soit l'ordre. Dans cet exemple, nous avons également présenté les paramètres sous forme verticale, ce qui est correct, et nous avons ajouté une virgule à la fin du dernier paramètre. (Ce qui, comme indiqué, est nouveau en PHP 8.0 et aide à prendre en charge exactement ce cas.).
Les noms de paramètres doivent être littéraux ; vous ne pouvez pas avoir une variable comme nom de paramètre.
Il est également possible de spécifier certains paramètres par leur nom et d'autres par leur position. Plus précisément, vous pouvez lister les paramètres par position jusqu'à ce que vous passiez aux arguments nommés, puis utiliser les arguments nommés par la suite. Ainsi, ceci est parfaitement valable :
<?php
array_fill(0,
value: 50,
count: 100,
);
Mais ceci ne l'est pas :
<?php
array_fill(
value: 50,
0,
count: 100,
);
Une question délicate concernant les paramètres nommés est celle les variadiques. Les "variadiques" sont le nom fantaisiste pour la capacité de passer un nombre variable de paramètres à une fonction. C'est à dire que vous avez longtemps pu faire ceci en PHP :
<?php
include_these($product, 1, 2, 3, 4, 5);
function include_these(Product $product, int ...$args)
{
// $args est maintenant un tableau d'entiers.
$vals = ['a', 'b'];
do_other_thing(...$vals);
}
Ici, l'opérateur ...
, affectueusement appelé "splat", soit regroupe les arguments dans un tableau, soit les répartit à partir d'un tableau, selon le contexte. Mais comment cela interagit-il avec les arguments nommés ?
La façon dont ils interagissent est, je dirais, ce à quoi vous vous attendriez logiquement. Un tableau indexé qui est étalé le sera en tant qu'arguments positionnels. Un tableau associatif qui est étalé sera étalé en tant qu'arguments nommés. Reprenons l'exemple précédent :
<?php
// Ceci est un tableau nommé, donc les valeurs seront mappées aux paramètres nommés.
$params = ['count' => 100, 'start_index' => 0, 'value' => 50];
array_fill(...$params);
Lors de la collecte d'arguments variadiques, ils seront collectés soit comme des valeurs de tableau numériquement indexées s'ils sont passés positionnellement, soit comme des valeurs de tableau à clés de chaîne s'ils sont passés par nom. Sachez que, si vous avez un paramètre variadique, cela signifie que vous pouvez avoir un tableau qui est un mélange de clés numériques et de clés nommées.
Cela ouvre également des possibilités intéressantes pour construire un appel de fonction dynamiquement, en construisant d'abord dynamiquement un tableau associatif, puis en appelant la fonction avec celui-ci en utilisant splat.
<?php
$args['value'] = $request->get('val') ?? 'a';
$args['start_index'] = 0;
$args['count'] = $config->getSetting('array_size');
$array = array_fill(...$args);
La principale objection aux arguments nommés a toujours été que les rendre "trop faciles" pour des fonctions avec beaucoup d'arguments encouragerait une mauvaise conception d'API. Par exemple, cet appel de méthode est incontestablement difficile à comprendre :
<?php
read_value($object, true, false, false, false, true, true, 7, false, true);
Une telle API devrait vraiment être repensée. La crainte est que les paramètres nommés permettraient aux concepteurs d'API de s'en tirer en disant "eh bien, utilisez simplement des noms, alors vous pouvez même ignorer toutes les valeurs par défaut dont vous ne vous souciez pas de cette façon". C'est vrai, mais cela manque aussi le fait que le problème est que l'API est trop compliquée.
Il n'y a pas de prévention forte ici autre que "ne l'utilisez pas comme une béquille pour créer de mauvaises API", ce qui pourrait être dit de presque toutes les fonctionnalités de langage.
Les paramètres nommés ont été sérieusement proposés pour la première fois en 2013, mais n'ont pas gagné en popularité avant aujourd'hui. Ce qui les a vraiment ramenés au premier plan, ce sont plusieurs discussions sur la façon de faciliter la construction d'objets. Plusieurs propositions ont été lancées début 2020 pour des syntaxes dédiées, uniques, pour des objets "struct" plus faciles, c'est-à-dire des objets qui sont juste une collection de propriétés probablement publiques.
Des langages comme Go et Rust facilitent la création de structs avec des paramètres nommés, car ils n'ont pas techniquement d'objets comme PHP. Ils ont une struct avec des propriétés, point, que vous pouvez même définir comme un littéral de struct, puis vous pouvez y accrocher des méthodes. Le résultat net est similaire mais pas tout à fait le même que le style d'objet Java/C++/PHP. Pourtant, beaucoup de gens ont été à juste titre jaloux de la facilité avec laquelle ces langages permettent de créer de nouvelles structures complexes.
Aucune des syntaxes uniques proposées n'a vraiment résolu le problème d'une manière qui "correspondait" à PHP. Plusieurs personnes, y compris moi-même, ont cependant noté qu'une approche plus robuste consisterait à résoudre les problèmes sous-jacents d'une manière qui permettrait à une syntaxe de construction d'objets plus agréable de "surgir" naturellement.
J'ai exposé l'argument en détail dans un article de blog en mars dernier. En bref, la promotion des propriétés du constructeur et les arguments nommés nous donneraient, ensemble, une syntaxe d'initialisation de struct. Le tout serait supérieur à la somme de ses parties. Heureusement, Nikita Popov, toujours très occupé, a été d'accord avec moi et a pris le relais..
Cela nous amène à l'un des rares endroits où les arguments nommés devraient vraiment être utilisés : la construction d'objets struct.
Pour illustrer, considérons un objet valeur pour un objet URL PSR-7.
<?php
class Url implements UrlInterface
{
public function __construct(
private string $scheme = 'https',
private ?string $authority = null,
private ?string $userInfo = null,
private string $host = '',
private ?int $port = 443,
private string $path = '',
private ?string $query = null,
private ?string $fragment = null,
) {}
}
Nous avons déjà vu comment la promotion du constructeur rend cela beaucoup plus facile à écrire. Les arguments nommés facilitent également grandement l'utilisation, car de nombreux paramètres peuvent légitimement être absents dans une URL valide, y compris ceux qui se trouvent au début de la liste, comme $authority
. En PHP 7.4, vous auriez dû l'utiliser comme ceci :
<?php
$url = new Url('https', null, null, 'platform.sh', 443, '/blog', null, 'latest');
Ce qui est... affreux, mais vous devez le faire pour accéder aux arguments ultérieurs. Avec les paramètres nommés, cela se réduit à cette alternative plus courte et plus auto-documentée :
<?php
$url = new Url(host: 'platform.sh', path: '/blog', fragment: 'latest');
// Ou si vous préférez verticalement :
$url = new Url(
path: '/blog',
host: 'platform.sh',
fragment: 'latest',
);
Et vous pouvez ensuite mettre les paramètres dans n'importe quel ordre. Les deux fonctionnalités se complètent pour rendre le travail avec des objets légers beaucoup plus propre que dans les versions précédentes. Les constructeurs d'objets valeur sont, je dirais, l'un des trois endroits clés où utiliser les arguments nommés.
La deuxième cible clé est dans les attributs :
<?php
class SomeController
{
#[Route('/path', name: 'action')]
public function someAction()
{
// ...
}
}
Les attributs sont syntaxiquement un appel de constructeur d'objet, qui est appelé paresseusement via l'API de réflexion si nécessaire. Parce que c'est un appel de constructeur, il peut faire (presque) tout ce qu'un appel de constructeur peut faire, y compris utiliser des arguments nommés.
En fait, je prédis que la plupart des utilisations d'attributs utiliseront des arguments nommés. Le but est de rendre des métadonnées flexibles disponibles dans la syntaxe du langage lui-même. De nombreuses utilisations d'annotations Doctrine aujourd'hui reposent très fortement sur des clés nommées, car elles sont plus auto-documentées et plus flexibles lorsqu'il y a de nombreux arguments facultatifs. Il est logique de s'attendre (et d'encourager) à ce que les attributs suivent le même modèle.
Le troisième endroit où je m'attends à ce que les arguments nommés soient largement utilisés est sur les fonctions où les paramètres sont légitimement nécessaires, et légitimement déroutants, et où il n'y a pas de moyen évident de contourner cela. L'exemple array_fill()
plus tôt est un bon exemple.
Un autre bon exemple ? Le canard préféré de tous, $haystack, $needle
vs. $needle, $haystack
. Les fonctions de chaînes utilisent généralement un ordre, les fonctions de tableaux utilisent généralement l'autre, pour des raisons qui ont un sens lorsque vous modélisez votre API sur le C, mais pas autrement. Maintenant, vous n'avez plus besoin de vous rappeler quel est l'ordre.
<?php
if (in_array(haystack: $arr, needle: 'A')) {
// ...
}
Est-ce l'ordre dans lequel ces paramètres se trouvent dans la fonction, en termes de position ? Qui s'en soucie ? L'appel de fonction spécifie exactement ce qui est passé, rendant l'ordre non pertinent. (Ils ne sont pas dans l'ordre, au cas où vous vous poseriez la question.)
Enfin, nous pouvons arrêter de nous plaindre de l'ordre des paramètres et de citer cela comme une raison pour laquelle PHP est mauvais (je suis sûr que les gens continueront à le faire ; ils auront juste encore plus tort qu'ils ne l'ont déjà).
Une mise en garde, cependant. Comme indiqué, les arguments nommés fonctionnent automatiquement pour toutes les fonctions et méthodes. Cela signifie que les noms de paramètres dans les fonctions, méthodes et interfaces sont désormais significatifs pour l'API. Les modifier peut casser les utilisateurs qui les appellent par leur nom. (Ce serait le bon moment pour revoir vos noms de paramètres, tout en vérifiant que vos bibliothèques sont prêtes pour PHP 8.0.)
La signification du nom de variable n'est pas, à ce stade, appliquée dans l'héritage. C'est une décision pragmatique pour éviter de casser trop de code existant, car la grande majorité des méthodes ne seront pas appelées avec des paramètres nommés 99.9% du temps, donc créer encore plus de casse pour le code existant qui pourrait renommer les arguments pour des raisons tout à fait logiques n'avait pas de sens. Python et Ruby ont adopté la même approche et n'ont pas rencontré de problèmes sérieux, ce que PHP a pris comme un bon signe qu'il était sûr de faire de même.
Il n'est pas surprenant que le les arguments nommés RFC nous viennent de Nikita Popov.