Si vous demandez aux développeurs PHP quel genre d'opportunité ils veulent voir en PHP, la plupart appellent des génériques.
Un support générique au niveau linguistique serait la meilleure solution. Mais, les réaliser est difficile . Nous espérons qu'un jour, le support natif fera partie de la langue, mais il faudra probablement plusieurs années pour attendre.
Cet article montrera comment, en utilisant les outils existants, dans certains cas avec des modifications minimes, nous pouvons obtenir la puissance des génériques en PHP dès maintenant.
De la part d'un traducteur: j'utilise délibérément du papier calque des "génériques" anglais, car Je n'ai jamais entendu dans la communication que quelqu'un l'appelait «programmation généralisée».
Contenu:
Quels sont les génériques
Cette section couvre une brève introduction aux génériques .
Liens de lecture:
- RFC pour l'ajout de génériques PHP
- Prise en charge générique dans Phan
- Génériques et modèles de psaume
Exemple le plus simple
Puisqu'il n'est actuellement pas possible de définir des génériques au niveau de la langue, nous devrons utiliser une autre grande opportunité - les définir dans les blocs de dock.
Nous utilisons déjà cette option dans de nombreux projets. Jetez un œil à cet exemple:
function createUsers(iterable $names): array { ... }
Dans le code ci-dessus, nous faisons ce qui est possible au niveau de la langue. Nous avons défini le paramètre $names
comme quelque chose qui pourrait être répertorié. Nous avons également indiqué que la fonction retournera un tableau. PHP TypeError
une TypeError
si les types de paramètres et la valeur de retour ne correspondent pas.
Docblock améliore la compréhension du code. $names
doivent être des chaînes et la fonction doit renvoyer un tableau d'objets User
. PHP lui-même ne fait pas de telles vérifications. Mais les IDE tels que PhpStorm comprennent cette notation et avertissent le développeur que le contrat supplémentaire n'a pas été respecté. En plus de cela, des outils d'analyse statique tels que Psaume, PHPStan et Phan peuvent valider l'exactitude des données transférées vers et depuis la fonction.
Génériques pour déterminer les clés et les valeurs des types énumérés
Ci-dessus est l'exemple le plus simple d'un générique. Des méthodes plus complexes incluent la possibilité de spécifier le type de ses clés, ainsi que le type de valeurs. Voici une façon de décrire cela:
function getUsers(): array { ... }
Il indique ici que le tableau renvoyé par getUsers
a des clés de chaîne et des valeurs de type User
.
Les analyseurs statiques tels que Psaume, PHPStan et Phan comprennent cette annotation et en tiennent compte lors de la vérification.
Considérez le code suivant:
function getUsers(): array { ... } function showAge(int $age): void { ... } foreach(getUsers() as $name => $user) { showAge($name); }
Les analyseurs statiques lancent un avertissement sur l'appel showAge
avec une erreur, comme ceci: L' Argument 1 of showAge expects int, string provided
.
Malheureusement, au moment de la rédaction, PhpStorm ne sait pas comment.
Génériques plus sophistiqués
Nous continuons à approfondir le sujet des génériques. Considérons un objet qui est une pile :
class Stack { public function push($item): void { ... } public function pop() { ... } }
La pile peut accepter tout type d'objet. Mais que se passe-t-il si nous voulons restreindre la pile aux seuls objets de type User
?
Psaume et Phan prennent en charge les annotations suivantes:
class Stack { public function push($item): void; public function pop(); }
Le docblock est utilisé pour transmettre des informations de type supplémentaires, par exemple:
$stack = new Stack(); Means that $userStack must only contain Users.
Psaume, lors de l'analyse du code suivant:
$userStack->push(new User()); $userStack->push("hello");
Se plaindra de la ligne 2 avec l'erreur Argument 1 of Stack::push expects User, string(hello) provided.
PhpStorm ne prend actuellement pas en charge cette annotation.
En fait, nous n'avons couvert qu'une partie des informations sur les génériques, mais pour le moment, cela suffit.
Comment implémenter des génériques sans prise en charge linguistique
Vous devez effectuer les étapes suivantes:
- Au niveau de la communauté, définissez des normes génériques dans les blocs de quai (par exemple, un nouveau PSR ou revenez au PSR-5)
- Ajoutez des annotations de dockblock à votre code
- Utilisez des IDE qui comprennent ces conventions pour effectuer une analyse statique en temps réel afin de trouver des incohérences.
- Utilisez des outils d'analyse statique (tels que Psaume) comme l'une des étapes de CI pour détecter les erreurs.
- Définissez une méthode pour transmettre des informations de type à des bibliothèques tierces.
Normalisation
Pour le moment, la communauté PHP a adopté officieusement ce format générique (ils sont pris en charge par la plupart des outils et leur signification est claire pour la plupart):
function getUsers(): array { ... }
Cependant, nous avons des problèmes avec des exemples simples comme celui-ci:
function getUsers(): array { ... }
Le psaume le comprend et sait de quel type est la clé et les valeurs du tableau retourné.
Au moment de la rédaction, PhpStorm ne comprend pas cela. En utilisant cette entrée, je manque la puissance de l'analyse statique en temps réel offerte par PhpStorm.
Considérez le code ci-dessous. PhpStorm ne comprend pas que $user
est de type User
et $name
est de type chaîne:
foreach(getUsers() as $name => $user) { ... }
Si je choisis Psaume comme outil d'analyse statique, je pourrais écrire ce qui suit:
function getUsers(): array { ... }
Le Psaume comprend tout cela.
PhpStorm sait que la variable $user
est de type User
. Mais, il ne comprend toujours pas que la clé du tableau fait référence à une chaîne. Phan et PHPStan ne comprennent pas les annotations spécifiques du psaume. Le maximum qu'ils comprennent dans ce code est le même que dans PhpStorm: le type de $user
Vous pourriez faire valoir que PhpStorm devrait simplement accepter le array<keyType, valueType>
accord array<keyType, valueType>
. Je ne suis pas d'accord avec vous, car Je crois que cette dictée des normes est la tâche de la langue et de la communauté, et les outils ne devraient que les suivre.
Je suppose que l'accord décrit ci-dessus sera chaleureusement reçu par la plupart de la communauté PHP. Celui qui s'intéresse aux génériques. Cependant, les choses deviennent beaucoup plus compliquées en ce qui concerne les modèles. Ni PHPStan ni PhpStorm ne prennent actuellement en charge les modèles. Contrairement au Psaume et à Phan. Leur objectif est similaire, mais si vous creusez plus profondément, vous vous rendrez compte que les implémentations sont légèrement différentes.
Chacune des options présentées est une sorte de compromis.
Autrement dit, il faut un accord sur le format d'enregistrement générique:
- Ils améliorent la vie des développeurs. Les développeurs peuvent ajouter des génériques à leur code et en bénéficier.
- Les développeurs peuvent utiliser les outils qu'ils préfèrent et basculer entre eux (outils) si nécessaire.
- Les créateurs d'outils peuvent créer ces mêmes outils, en comprenant les avantages pour la communauté et sans craindre que quelque chose ne change ou qu'ils soient accusés d'une «mauvaise approche».
Le psaume possède toutes les fonctionnalités nécessaires pour vérifier les génériques. Phan aime aussi ça.
Je suis sûr que PhpStorm introduira des génériques dès que la communauté proposera un accord de format unique.
Prise en charge de code tiers
La dernière partie du puzzle générique consiste à ajouter la prise en charge des bibliothèques tierces.
Avec un peu de chance, dès que la norme de définition générique apparaîtra, la plupart des bibliothèques l'implémenteront. Cependant, cela ne se produira pas immédiatement. Certaines bibliothèques sont utilisées, mais n'ont pas de support actif. Lorsque vous utilisez des analyseurs statiques pour valider des types dans des génériques, il est important que toutes les fonctions que ces génériques acceptent ou retournent soient définies.
Que se passe-t-il si votre projet repose sur des bibliothèques tierces qui n'ont pas de support générique?
Heureusement, ce problème a déjà été résolu et les fonctions de remplacement sont la solution. Psaume, Phan et PhpStorm prennent en charge les talons.
Les stubs sont des fichiers ordinaires qui contiennent des signatures de fonctions et de méthodes, mais ne les implémentent pas. En ajoutant des blocs d'ancrage aux stubs, les outils d'analyse statique obtiennent les informations supplémentaires dont ils ont besoin. Par exemple, si vous avez une classe de pile sans conseils de type et génériques comme celui-ci.
class Stack { public function push($item) { } public function pop() { } }
Vous pouvez créer un fichier de raccord qui a des méthodes identiques, mais avec l'ajout de blocs d'ancrage et sans implémentation de fonctions.
class Stack { public function push($item); public function pop(); }
Lorsque l'analyseur statique voit la classe de pile, il prend les informations de type du stub, pas du code réel.
La possibilité de partager simplement le code des stubs (par exemple, via le compositeur) serait extrêmement utile, car permettrait de partager le travail accompli.
Etapes supplémentaires
La communauté doit s'éloigner des accords et établir des normes.
Peut-être que la meilleure option serait un PSR générique?
Ou peut-être que les créateurs des principaux analyseurs statiques, PhpStorm, d'autres IDE et toutes les personnes impliquées dans le développement de PHP (pour le contrôle) pourraient développer une norme que tout le monde utiliserait.
Dès que la norme apparaîtra, tout le monde pourra aider à l'ajout de génériques aux bibliothèques et projets existants, en créant des demandes d'extraction. Et lorsque cela n'est pas possible, les développeurs peuvent écrire et partager des talons.
Lorsque tout est terminé, nous pouvons utiliser des outils comme PhpStorm pour vérifier les génériques en temps réel pendant que nous écrivons le code. Nous pouvons utiliser des outils d'analyse statique dans le cadre de notre CI comme garantie de sécurité.
Les génériques peuvent également être implémentés en PHP (enfin, presque).
Limitations
Il existe un certain nombre de limitations. PHP est un langage dynamique qui vous permet de faire beaucoup de choses "magiques", comme celles- ci . Si vous utilisez trop de magie PHP, il peut arriver que les analyseurs statiques ne puissent pas extraire avec précision tous les types du système. Si certains types sont inconnus, les outils ne pourront pas utiliser correctement les génériques dans tous les cas.
Cependant, l'application principale de cette analyse est de valider votre logique métier. Si vous écrivez du code propre, vous ne devez pas utiliser trop de magie.
Pourquoi ne pas simplement ajouter des génériques à la langue?
Ce serait la meilleure option. PHP a du code open source, et personne ne vous dérange pour cloner les sources et implémenter les génériques!
Et si je n'ai pas besoin de génériques?
Ignorez simplement tout ce qui précède. L'un des principaux avantages de PHP est qu'il est flexible dans le choix du niveau approprié de complexité d'implémentation en fonction de ce que vous créez. Avec un code à usage unique, vous n'avez pas besoin de penser à des choses comme la dactylographie. Mais dans les grands projets, il vaut la peine d'utiliser ces opportunités.
Merci à tous ceux qui ont lu cet endroit. Je serai heureux de vos commentaires dans le PM.
UPD : ghost404 dans les commentaires a noté que PHPStan de la version 0.12.x comprend les annotations de psaume et prend en charge les génériques