Analyse statique du code PHP en utilisant PHPStan, Phan et Psalm comme exemples



Badoo existe depuis plus de 12 ans. Nous avons beaucoup de code PHP (des millions de lignes) et probablement même des lignes écrites il y a 12 ans ont été conservées. Nous avons du code écrit à l'époque de PHP 4 et PHP 5. Nous téléchargeons le code deux fois par jour, et chaque mise en page contient environ 10-20 tâches. De plus, les programmeurs peuvent publier des correctifs urgents - de petits changements. Et le jour de tels patchs, nous en gagnons quelques dizaines. En général, notre code évolue très activement.

Nous recherchons constamment des opportunités pour accélérer le développement et améliorer la qualité du code. Nous avons donc décidé un jour d'implémenter l'analyse de code statique. Ce qui en est sorti, lu sous la coupe.

Types stricts: pourquoi nous ne l'utilisons pas encore


Une fois, une discussion a commencé dans notre chat PHP d'entreprise. L'un des nouveaux employés a expliqué comment, sur le lieu de travail précédent, ils avaient introduit les conseils obligatoires de type strict_types + scalaire pour l'ensemble du code - et cela a considérablement réduit le nombre de bogues en production.

La plupart des anciens bavardeurs étaient contre une telle innovation. La principale raison était que PHP n'a pas de compilateur qui vérifie tous les types dans le code au moment de la compilation, et si vous n'avez pas une couverture à 100% du code avec des tests, alors il y a toujours un risque que des erreurs apparaissent lors de la production, ce que nous n'avons pas veulent permettre.

Bien sûr, strict_types trouvera un certain pourcentage de bogues causés par une incompatibilité de type et comment PHP convertit "silencieusement" les types. Mais de nombreux programmeurs PHP expérimentés savent déjà comment fonctionne le système de type en PHP, par quelles règles la conversion de type se produit, et dans la plupart des cas, ils écrivent du code de travail correct.

Mais l'idée d'avoir un certain système montrant où dans le code il y a une incompatibilité de type, nous avons aimé. Nous avons pensé à des alternatives à strict_types.

Au début, nous voulions même patcher PHP. Nous voulions que si la fonction prend une sorte de type scalaire (disons int), et qu'un autre type scalaire (comme float) entre, alors TypeError (qui est une exception en soi) ne serait pas levée, mais une conversion de type se produirait, ainsi que la journalisation de cet événement dans error.log. Cela nous permettrait de trouver tous les endroits où nos hypothèses sur les types sont incorrectes. Mais un tel correctif nous a semblé risqué, et même il pourrait y avoir des problèmes de dépendances externes, pas prêts pour un tel comportement.

Nous avons abandonné l'idée de patcher PHP, mais avec le temps, tout a coïncidé avec les premières versions de l'analyseur statique Phan, les premiers commits ayant été effectués par Rasmus Lerdorf lui-même. Nous avons donc eu l'idée d'essayer des analyseurs de code statique.

Qu'est-ce que l'analyse de code statique?


Les analyseurs de code statique lisent simplement le code et essaient d'y trouver des erreurs. Ils peuvent effectuer des vérifications très simples et évidentes (par exemple, pour l'existence de classes, de méthodes et de fonctions et d'autres plus délicates (par exemple, rechercher des incohérences de types, des conditions de concurrence ou des vulnérabilités dans le code). La clé est que les analyseurs n'exécutent pas de code - ils analyser le texte du programme et le vérifier pour des erreurs typiques (et pas si).

L'exemple le plus évident d'un analyseur de code PHP statique est les inspections dans PHPStorm: lorsque vous écrivez du code, il met en évidence des appels incorrects aux fonctions, méthodes, incompatibilités de types de paramètres, etc. Cependant, PHPStorm n'exécute pas votre code PHP - il ne fait que l'analyser.

Je note que dans cet article, nous parlons d'analyseurs qui recherchent des erreurs dans le code. Il existe une autre classe d'analyseurs - ils vérifient le style d'écriture du code, la complexité cyclomatique, la taille des méthodes, la longueur des lignes, etc. Nous ne considérons pas de tels analyseurs ici.

Bien que tout ce que les analyseurs que nous envisageons ne trouve pas est précisément une erreur. Par erreur, je veux dire le code que Fatal créera en production. Très souvent, ce que les analyseurs trouvent est plus probablement une inexactitude. Par exemple, un type de paramètre incorrect peut être spécifié dans PHPDoc. Cette imprécision n'affecte pas le fonctionnement du code, mais par la suite le code évoluera - un autre programmeur peut faire une erreur.

Analyseurs de code PHP existants


Il existe trois analyseurs de code PHP populaires:

  1. PHPStan .
  2. Psaume .
  3. Phan .

Et il y a Exakat , que nous n'avons pas essayé.

Côté utilisateur, les trois analyseurs sont identiques: vous les installez (très probablement via Composer), vous les configurez, après quoi vous pouvez démarrer l'analyse de l'ensemble du projet ou du groupe de fichiers. En règle générale, l'analyseur peut magnifiquement afficher les résultats dans la console. Vous pouvez également générer les résultats au format JSON et les utiliser dans CI.

Les trois projets se développent désormais activement. Leurs responsables sont très actifs pour répondre aux problèmes sur GitHub. Souvent, le premier jour après avoir créé un ticket, ils y réagissent au moins (commenter ou mettre une balise comme bug / amélioration). De nombreux bugs que nous avons trouvés ont été corrigés en quelques jours. Mais j'aime particulièrement le fait que les responsables de projet communiquent activement entre eux, se signalent des bogues et envoient des demandes de tirage.

Nous avons implémenté et utilisé les trois analyseurs. Chacun a ses propres nuances, ses propres bugs. Mais l'utilisation de trois analyseurs en même temps permet de mieux comprendre où se situe le vrai problème et où se trouve le faux positif.

Ce que les analyseurs peuvent faire


Les analyseurs ont de nombreuses fonctionnalités communes, nous allons donc d'abord voir ce qu'ils peuvent tous faire, puis passer aux fonctionnalités de chacun d'eux.

Chèques standard


Bien sûr, les analyseurs effectuent toutes les vérifications de code standard pour le fait que:

  • Le code ne contient pas d'erreurs de syntaxe;
  • toutes les classes, méthodes, fonctions, constantes existent;
  • des variables existent;
  • en PHPDoc, les indices sont vrais.

En outre, les analyseurs vérifient le code pour les arguments et les variables inutilisés. Beaucoup de ces erreurs conduisent à de vrais fatals dans le code.

À première vue, il peut sembler que les bons programmeurs ne commettent pas de telles erreurs, mais parfois nous sommes pressés, parfois copier-coller, parfois nous sommes simplement inattentifs. Et dans de tels cas, ces contrôles économisent beaucoup.

Vérifications des types de données


Bien entendu, les analyseurs statiques effectuent également des contrôles standard concernant les types de données. S'il est écrit dans le code que la fonction accepte, disons int, alors l'analyseur vérifiera s'il y a des endroits où un objet est passé à cette fonction. Pour la plupart des analyseurs, vous pouvez configurer la gravité du test et simuler strict_types: vérifiez qu'aucune chaîne ou booléen n'est transmis à cette fonction.

En plus des contrôles standard, les analyseurs ont encore beaucoup à faire.

Types d'unions

Tous les analyseurs prennent en charge le concept de types Union. Supposons que vous ayez une fonction comme:

/** * @var string|int|bool $yes_or_no */ function isYes($yes_or_no) :bool {    if (\is_bool($yes_or_no)) {         return $yes_or_no;     } elseif (is_numeric($yes_or_no)) {         return $yes_or_no > 0;     } else {         return strtoupper($yes_or_no) == 'YES';     } } 

Son contenu n'est pas très important - le type de la string|int|bool paramètre d'entrée string|int|bool est important. Autrement dit, la variable $yes_or_no est soit une chaîne, soit un entier, soit un Boolean .

En utilisant PHP, ce type de paramètre de fonction ne peut pas être décrit. Mais en PHPDoc, c'est possible, et de nombreux éditeurs (comme PHPStorm) le comprennent.

Dans les analyseurs statiques, ce type est appelé type d'union et ils sont très bons pour vérifier ces types de données. Par exemple, si nous écrivions la fonction ci-dessus comme ceci (sans vérifier le Boolean ):

 /** * @var string|int|bool $yes_or_no */ function isYes($yes_or_no) :bool {    if (is_numeric($yes_or_no)) {        return $yes_or_no > 0;    } else {        return strtoupper($yes_or_no) == 'YES';    } } 

les analyseurs verraient qu'une chaîne ou un booléen pourrait arriver à strtoupper et retourner une erreur - vous ne pouvez pas passer un booléen à strtoupper.

Ce type de vérification aide les programmeurs à gérer correctement les erreurs ou les situations où une fonction ne peut pas renvoyer de données. Nous écrivons souvent des fonctions qui peuvent renvoyer certaines données ou null :

 // load()  null   \User $User = UserLoader::load($user_id); $User->getName(); 

Dans le cas d'un tel code, l'analyseur vous dira que la variable $User ici peut être null et que ce code peut entraîner la mort.

Tapez false

Dans le langage PHP lui-même, il existe de nombreuses fonctions qui peuvent renvoyer une valeur ou une valeur fausse. Si nous devions écrire une telle fonction, comment documenterions-nous son type?

          /** @return resource|bool */ function fopen(...) {       … } 

Formellement, tout semble être vrai ici: fopen renvoie soit une ressource soit une false (qui est de type Boolean ). Mais lorsque nous disons qu'une fonction renvoie une sorte de type de données, cela signifie qu'elle peut renvoyer n'importe quelle valeur d'un ensemble appartenant à ce type de données. Dans notre exemple, pour l'analyseur, cela signifie que fopen() peut retourner true . Et, par exemple, dans le cas d'un tel code:

 $fp = fopen('some.file','r'); if($fp === false) {     return false; } fwrite($fp, "some string"); 

les analyseurs se plaindraient que fwrite accepte la première ressource de paramètre, et nous lui passons bool (car l'analyseur voit qu'une vraie option est possible). Pour cette raison, tous les analyseurs @return false|resource un type de données «artificiel» comme false , et dans notre exemple, nous pouvons écrire @return false|resource . PHPStorm comprend également cette description de type.

Formes de tableau

Très souvent, les tableaux en PHP sont utilisés comme type d' record - une structure avec une liste claire de champs, où chaque champ a son propre type. Bien sûr, de nombreux programmeurs utilisent déjà des classes pour cela. Mais nous avons beaucoup de code hérité dans Badoo, et les tableaux y sont activement utilisés. Et il arrive également que les programmeurs soient trop paresseux pour créer une classe distincte pour une structure unique, et dans de tels endroits, les tableaux sont également souvent utilisés.

Le problème avec de tels tableaux est qu'il n'y a pas de description claire de cette structure (une liste de champs et leurs types) dans le code. Les programmeurs peuvent commettre des erreurs lorsqu'ils travaillent avec une telle structure: oubliez les champs obligatoires ou ajoutez des touches «gauche», ce qui rend le code encore plus confus.

Les analyseurs vous permettent d'entrer une description de ces structures:

 /** @param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl(array $parsed_url) { … } 

Dans cet exemple, nous avons décrit un tableau avec trois champs de chaîne: scheme, host et path . Si à l'intérieur de la fonction nous nous tournons vers un autre champ, l'analyseur affichera une erreur.

Si vous ne décrivez pas les types, les analyseurs essaieront de «deviner» la structure du tableau, mais, comme le montre la pratique, ils ne réussissent pas vraiment avec notre code. :)

Cette approche présente un inconvénient. Supposons que vous ayez une structure activement utilisée dans le code. Vous ne pouvez pas déclarer un pseudotype en un seul endroit et l'utiliser ensuite partout. Vous devrez enregistrer PHPDoc avec la description du tableau partout dans le code, ce qui est très gênant, surtout s'il y a beaucoup de champs dans le tableau. Il sera également problématique de modifier ce type ultérieurement (ajouter et supprimer des champs).

Description des types de clés de tableau

En PHP, les clés de tableau peuvent être des entiers et des chaînes. Les types peuvent parfois être importants pour l'analyse statique (et pour les programmeurs également). Les analyseurs statiques vous permettent de décrire les clés de tableau en PHPDoc:

 /** @var array<int, \User> $users */ $users = UserLoaders::loadUsers($user_ids); 

Dans cet exemple, en utilisant PHPDoc, nous avons ajouté un indice selon lequel dans le tableau $users les clés sont des entiers et les valeurs sont des objets de la classe \User . Nous pourrions décrire le type comme \ User []. Cela indiquerait à l'analyseur qu'il y a des objets dans la classe \User du tableau, mais ne nous dirait rien sur le type de clés.

PHPStorm prend en charge ce format pour décrire les tableaux à partir de la version 2018.3.

Votre espace de noms en PHPDoc

PHPStorm (et d'autres éditeurs) et les analyseurs statiques peuvent comprendre PHPDoc différemment. Par exemple, les analyseurs prennent en charge ce format:

 /** @param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl($parsed_url) { … } 

Mais PHPStorm ne le comprend pas. Mais nous pouvons écrire comme ceci:

 /** * @param array $parsed_url * @phan-param array{scheme:string,host:string,path:string} $parsed_url * @psalm-param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl($parsed_url) { … } 

Dans ce cas, les analyseurs et PHPStorm seront satisfaits. PHPStorm utilisera @param et les analyseurs utiliseront leurs propres balises PHPDoc.

Vérifications des fonctionnalités PHP


Ce type de test est mieux illustré par l'exemple.

Savons-nous tous ce que la fonction explode () peut renvoyer? Si vous regardez la documentation, il semble qu'elle renvoie un tableau. Mais si vous l'examinez plus attentivement, nous verrons qu'il peut aussi retourner faux. En fait, il peut renvoyer à la fois null et une erreur si vous lui transmettez les mauvais types, mais passer la mauvaise valeur avec le mauvais type de données est déjà une erreur, donc cette option n'est plus intéressante pour nous maintenant.

Formellement, du point de vue de l'analyseur, si une fonction peut retourner false ou un tableau, alors, très probablement, le code devrait vérifier false. Mais la fonction explode () ne renvoie false que si le délimiteur (premier paramètre) est égal à une chaîne vide. Souvent, il est explicitement écrit dans le code, et les analyseurs peuvent vérifier qu'il n'est pas vide, ce qui signifie qu'à cet endroit la fonction explode () renvoie avec précision un tableau et qu'une fausse vérification n'est pas nécessaire.

PHP a quelques fonctionnalités. Les analyseurs ajoutent progressivement des vérifications appropriées ou les améliorent, et nous, les programmeurs, n'avons plus besoin de mémoriser toutes ces fonctionnalités.

Nous passons à la description d'analyseurs spécifiques.

PHPStan


Développement d'un certain Ondřej Mirtes de la République tchèque. Développé activement depuis fin 2016.

Pour commencer à utiliser PHPStan, vous devez:

  1. Installez-le (la façon la plus simple de le faire est via Composer).
  2. (facultatif) Configurez.
  3. Dans le cas le plus simple, lancez simplement:

vendor/bin/phpstan analyse ./src

(au lieu de src peut src avoir une liste de fichiers spécifiques que vous souhaitez vérifier).

PHPStan lira le code PHP des fichiers transférés. S'il rencontre des classes inconnues, il essaiera de les charger avec le chargement automatique et par réflexion pour comprendre leur interface. Vous pouvez également transférer le chemin d'accès au fichier Bootstrap à travers lequel vous configurez le chargement automatique et joindre des fichiers supplémentaires pour simplifier l'analyse PHPStan.

Caractéristiques clés:

  1. Il est possible d'analyser non pas la totalité de la base de code, mais seulement une partie - des classes inconnues PHPStan tentera de charger le chargement automatique.
  2. Si, pour une raison quelconque, certaines de vos classes ne sont pas dans le chargement automatique, PHPStan ne pourra pas les trouver et donnera une erreur.
  3. Si vous utilisez activement des méthodes magiques via __call / __get / __set , vous pouvez écrire un plugin pour PHPStan. Des plugins pour Symfony, Doctrine, Laravel, Mockery, etc. existent déjà.
  4. En fait, PHPStan effectue le chargement automatique non seulement pour les classes inconnues, mais en général pour tout le monde. Nous avons beaucoup d'ancien code écrit avant l'apparition des classes anonymes, lorsque nous créons une classe dans un fichier, puis l'instancions instantanément et, éventuellement, appelons même certaines méthodes. Le chargement automatique ( include ) de ces fichiers entraîne des erreurs, car le code n'est pas exécuté dans un environnement normal.
  5. Configs au format néon (je n'ai jamais entendu dire qu'un tel format était utilisé ailleurs).
  6. Il n'y a pas de support pour leurs balises PHPDoc comme @phpstan-var, @phpstan-return , etc.

Une autre caractéristique est que les erreurs ont du texte, mais il n'y a pas de type. Autrement dit, le texte d'erreur vous est renvoyé, par exemple:

  • Method \SomeClass::getAge() should return int but returns int|null
  • Method \SomeOtherClass::getName() should return string but returns string|null

Dans cet exemple, les deux erreurs concernent essentiellement la même chose: la méthode doit renvoyer un type, mais en réalité, elle renvoie l'autre. Mais les textes des erreurs sont différents, bien que similaires. Par conséquent, si vous souhaitez filtrer les erreurs dans PHPStan, ne le faites que par le biais d'expressions régulières.

À titre de comparaison, dans d'autres analyseurs, les erreurs ont un type. Par exemple, dans Phan, une telle erreur est de type PhanPossiblyNullTypeReturn , et vous pouvez spécifier dans la configuration que vous n'avez pas besoin de vérifier ces erreurs. De plus, ayant le type d'erreur, il est possible, par exemple, de collecter facilement des statistiques sur les erreurs.

Comme nous n'utilisons pas Laravel, Symfony, Doctrine et des solutions similaires, et que nous utilisons rarement des méthodes magiques dans notre code, la principale caractéristique de PHPStan s'est avérée non réclamée pour nous. ; (De plus, du fait que PHPStan inclut toutes les classes en cours de vérification, parfois son analyse ne fonctionne tout simplement pas sur notre base de code.

Cependant, PHPStan nous reste utile:

  • Si vous devez vérifier plusieurs fichiers, PHPStan est sensiblement plus rapide que Phan et un peu (20-50%) plus rapide que Psalm.
  • Les rapports PHPStan facilitent la recherche de false-positive dans d'autres analyseurs. Habituellement, s'il y a un fatal explicite dans le code, il est affiché par tous les analyseurs (ou au moins deux des trois).


Mise à jour:
L'auteur de PHPStan Ondřej Mirtes a également lu notre article et nous a dit que PhpStan, comme Psaume, a un site Web avec un «bac à sable»: https://phpstan.org/ . C'est très pratique pour les rapports de bogues: vous reproduisez l'erreur dans et donnez un lien dans GitHub.

Phan


Développé par Etsy. Commet d'abord de Rasmus Lerdorf.

Des trois en question, Phan est le seul véritable analyseur statique (dans le sens où il n'exécute aucun de vos fichiers - il analyse l' intégralité de votre base de code, puis analyse ce que vous dites). Même pour analyser plusieurs fichiers dans notre base de code, il a besoin d'environ 6 Go de RAM, et ce processus prend de quatre à cinq minutes. Mais une analyse complète de la base de code entière prend environ six à sept minutes. A titre de comparaison, le Psaume l'analyse en quelques dizaines de minutes. Et à partir de PHPStan, nous n'avons pas été en mesure de réaliser une analyse complète de l'intégralité de la base de code car elle comprend des classes d'inclusion.

L'expérience Phan est double. D'une part, c'est l'analyseur le plus stable et de haute qualité, il en trouve beaucoup et il y a moins de problèmes avec lui quand il est nécessaire d'analyser toute la base de code. En revanche, il présente deux caractéristiques désagréables.

Sous le capot, Phan utilise l'extension php-ast. Apparemment, c'est l'une des raisons pour lesquelles l'analyse de la base de code entière est relativement rapide. Mais php-ast montre la représentation interne de l'arbre AST telle qu'elle apparaît en PHP lui-même. Et en PHP lui-même, l'arborescence AST ne contient pas d'informations sur les commentaires qui se trouvent à l'intérieur de la fonction. Autrement dit, si vous avez écrit quelque chose comme:

 /** * @param int $type */ function doSomething($type) {   /** @var \My\Object $obj **/   $obj = MyFactory::createObjectByType($type);   … } 

puis à l'intérieur de l'arborescence AST, il y a des informations sur le PHPDoc externe pour la fonction doSomething() , mais il n'y a aucune information d'aide PHPDoc à l'intérieur de la fonction. Et, en conséquence, Phan ne sait rien d'elle non plus. Il s'agit de la cause la plus courante de false-positive à Phan. Il existe quelques recommandations sur la façon d'insérer des info-bulles (via des chaînes ou des assert-s), mais, malheureusement, elles sont très différentes de ce à quoi nos programmeurs sont habitués. En partie, nous avons résolu ce problème en écrivant un plugin pour Phan. Mais les plugins seront discutés ci-dessous.

La deuxième caractéristique désagréable est que Phan n'analyse pas bien les propriétés des objets. Voici un exemple:

 class A { /** * @var string|null */ private $a; public function __construct(string $a = null) {      $this->a = $a; } public function doSomething() {      if ($this->a && strpos($this->a, 'a') === 0) {          var_dump("test1");      } } } 

Dans cet exemple, Phan vous dira qu'en strpos, vous pouvez passer null. Vous pouvez en savoir plus sur ce problème ici: https://github.com/phan/phan/issues/204 .

Résumé Malgré quelques difficultés, Phan est un développement très cool et utile. En plus de ces deux types de false-positive , il ne fait presque pas d'erreurs, ou fait des erreurs, mais sur un code vraiment complexe. Nous avons également aimé que la configuration soit dans un fichier PHP - cela donne une certaine flexibilité. Phan sait également travailler en tant que serveur de langue, mais nous n'avons pas utilisé cette fonctionnalité, car PHPStorm nous suffit.

Plugins


Phan possède une API de développement de plugins bien développée. Vous pouvez ajouter vos propres vérifications, améliorer l'inférence de type pour votre code. Cette API contient de la documentation , mais c'est particulièrement cool qu'il y ait déjà des plugins qui fonctionnent et qui peuvent être utilisés comme exemples.

Nous avons réussi à écrire deux plugins. Le premier était destiné à un contrôle unique. Nous voulions évaluer dans quelle mesure notre code est prêt pour PHP 7.3 (en particulier, pour savoir s'il a case-insensitive constantes case-insensitive à la case-insensitive ). Nous étions presque sûrs qu'il n'y avait pas de telles constantes, mais tout pouvait arriver en 12 ans - cela devrait être vérifié. Et nous avons écrit un plugin pour Phan qui jurerait si le troisième paramètre était utilisé dans define() .

Le plugin est très simple
 <?php declare(strict_types=1); use Phan\AST\ContextNode; use Phan\CodeBase; use Phan\Language\Context; use Phan\Language\Element\Func; use Phan\PluginV2; use Phan\PluginV2\AnalyzeFunctionCallCapability; use ast\Node; class DefineThirdParamTrue extends PluginV2 implements AnalyzeFunctionCallCapability { public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array {   $define_callback = function (       CodeBase $code_base,                  Context $context,                  Func $function,                  array $args    ) {      if (\count($args) < 3) {         return;      }       $this->emitIssue(       $code_base,      $context,      'PhanDefineCaseInsensitiv',      'Define with 3 arguments',      []      );    };    return [          'define' => $define_callback,    ]; } } return new DefineThirdParamTrue(); 



Dans Phan, différents plugins peuvent être suspendus sur différents événements. En particulier, les plugins avec l'interface AnalyzeFunctionCallCapability déclenchés lorsqu'un appel de fonction est analysé. Dans ce plugin, nous avons fait en sorte que lorsque nous appelons la fonction define() , notre fonction anonyme soit appelée, qui vérifie que define() pas plus de deux arguments. Ensuite, nous avons juste commencé Phan, trouvé tous les endroits où define() été appelé avec trois arguments, et nous nous sommes assurés que nous n'avions pas de case-insensitive- .

En utilisant le plugin, nous avons également partiellement résolu le problème des false-positive lorsque Phan ne voit pas d'indices PHPDoc dans le code.

Nous utilisons souvent des méthodes d'usine qui prennent une constante en entrée et en créent un objet. Souvent, le code ressemble à ceci:

 /** @var \Objects\Controllers\My $Object */ $Object = \Objects\Factory::create(\Objects\Config::MY_CONTROLLER); 

Phan ne comprend pas ces conseils PHPDoc, mais dans ce code, la classe d'objet peut être obtenue à partir du nom de la constante passée à la méthode create() . Phan vous permet d'écrire un plugin qui se déclenche lorsqu'il analyse la valeur de retour d'une fonction. Et avec ce plugin, vous pouvez dire à l'analyseur quel type la fonction retourne dans cet appel.

Un exemple de ce plugin est plus complexe. Mais il y a un bon exemple dans le code Phan dans vendor/phan/phan/src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php.

Dans l'ensemble, nous sommes très satisfaits de l'analyseur Phan. Les false-positive listés ci-dessus nous ont partiellement appris (dans des cas simples, avec du code simple) à filtrer. Après cela, Phan est devenu un analyseur presque de référence. Cependant, la nécessité d'analyser immédiatement toute la base de code (temps et beaucoup de mémoire) complique toujours le processus de sa mise en œuvre.

Psaume


Psalm est un développement de Vimeo. Honnêtement, je ne savais même pas que Vimeo utilise PHP avant d'avoir vu Psaume.

Cet analyseur est le plus jeune de nos trois. Quand j'ai lu la nouvelle que Vimeo avait sorti Psalm, j'étais perplexe: "Pourquoi investir dans Psalm si vous avez déjà Phan et PHPStan?" Mais il s'est avéré que le Psaume a ses propres caractéristiques utiles.

Le psaume a suivi les traces de PHPStan: vous pouvez également lui donner une liste de fichiers à analyser, et il les analysera, et connectera les classes qui ne sont pas trouvées avec un chargement automatique. Dans le même temps, il ne connecte que les classes qui n'ont pas été trouvées, et les fichiers que nous avons demandés pour l'analyse ne seront pas inclus (c'est différent de PHPStan). La configuration est stockée dans un fichier XML (pour nous, c'est probablement un inconvénient, mais pas très critique).

Psaume a un site en bac à sable où vous pouvez écrire du code PHP et l'analyser. C'est très pratique pour les rapports de bugs: vous reproduisez l'erreur sur le site et donnez le lien dans GitHub. Et d'ailleurs, le site décrit tous les types d'erreurs possibles. A titre de comparaison: dans PHPStan, les erreurs n'ont pas de types, et dans Phan elles le sont, mais il n'y a pas de liste unique qui puisse être trouvée.

Nous avons également aimé que lors de la sortie des erreurs, Psaume affiche immédiatement les lignes de code où elles ont été trouvées. Cela simplifie considérablement la lecture des rapports.

Mais la caractéristique la plus intéressante de Psalm est peut-être ses balises PHPDoc personnalisées, qui vous permettent d'améliorer l'analyse (en particulier la définition des types). Nous listons les plus intéressants d'entre eux.

@ psalm-ignore-nullable-return


Il arrive que formellement une méthode puisse retourner null , mais le code est déjà organisé de telle manière que cela n'arrive jamais. Dans ce cas, il est très pratique que vous puissiez ajouter un tel indice PHPDoc à la méthode / fonction - et Psaume considérera que null pas retourné.

Un indice similaire existe pour false: @psalm-ignore-falsable-return .

Types de fermeture


Si vous vous êtes déjà intéressé à la programmation fonctionnelle, vous avez peut-être remarqué qu'une fonction peut souvent renvoyer une autre fonction ou prendre une fonction en paramètre. En PHP, ce style peut être très déroutant pour vos collègues, et l'une des raisons est que PHP n'a pas de normes pour documenter de telles fonctions. Par exemple:

 function my_filter(array $ar, \Closure $func) { … } 

Comment un programmeur peut-il comprendre l'interface de la fonction dans le deuxième paramètre? Quels paramètres faut-il prendre? Que doit-elle retourner?

Psaume prend en charge la syntaxe pour décrire les fonctions en PHPDoc:

 /** * @param array $ar * @psalm-param Closure(int):bool $func */ function my_filter(array $ar, \Closure $func) { … } 

Avec une telle description, il est déjà clair que vous devez passer une fonction anonyme à my_filter , qui acceptera un int et renverra bool. Et, bien sûr, le Psaume vérifiera que vous avez exactement une telle fonction passée dans votre code.

Enums


Supposons que vous ayez une fonction qui accepte un paramètre de chaîne et que vous ne puissiez y passer que certaines chaînes:

 function isYes(string $yes_or_no) : bool {     $yes_or_no = strtolower($yes_or_no)     switch($yes_or_no)  {           case 'yes':                 return true;          case 'no':                 return false;           default:                throw new \InvalidArgumentException(…);     } } 

Le psaume vous permet de décrire le paramètre de cette fonction comme ceci:

 /** @psalm-param 'Yes'|'No' $yes_or_no **/ function isYes(string $yes_or_no) : bool { … } 

Dans ce cas, le Psaume essaiera de comprendre quelles valeurs spécifiques sont transmises à cette fonction et générera des erreurs s'il existe des valeurs autres que Yes et No

En savoir plus sur enum ici .

Tapez les alias


Plus tôt dans la description des array shapes de array shapes j'ai mentionné que bien que les analyseurs vous permettent de décrire la structure des tableaux, il n'est pas très pratique de l'utiliser, car la description du tableau doit être copiée à différents endroits. La bonne solution, bien sûr, consiste à utiliser des classes au lieu de tableaux. Mais dans le cas de nombreuses années d'héritage, ce n'est pas toujours possible.

, , , :

  • ;
  • closure;
  • union- (, );
  • enum.

, , PHPDoc , , . Psalm . alias PHPDoc alias . , : PHP-. . , Psalm.

Generics aka templates


. , :

 function identity($x) { return $x; } 

? ? ?

, , , — mixed , .

mixed — . , . , identity() / , : , . -. , :

 $i = 5; // int $y = identity($i); 

(int) , , $y ( int ).

? Psalm PHPDoc-:

 /** * @template T * @psalm-param T $x * @psalm-return T */ function identity($x) { $return $x; } 

templates Psalm , / .

Psalm templates:

vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericFunctions.php ;
vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericClasses.php .

Phan, : https://github.com/phan/phan/wiki/Generic-Types .

, Psalm . , «» . , Psalm , , Phan PHPStan. .

PHPStorm


: , . , , .

. Phan, language server. PHPStorm, , .

, , PHPStorm ( ), . — Php Inspections (EA Extended). — , , . , . , scopes - scopes.

, deep-assoc-completion . .

Badoo


?

, .

, . , , git diff / , , () . , .

, : - git diff . . , . . , , , , .

, , :



false-positive . , , Phan , , . , - Phan , , .

QA


:



— , , , . :

  • 100% ( , );
  • , code review;
  • , .

strict types . , strict types , :

  • , strict types , ;
  • , (, , );
  • , PHP (, union types , PHP);
  • strict types , .

:


, . .

-, , , - , .

-, , — , , PHPDoc. — .

-, . , - , PHPDoc. :)

, , . , .

Source: https://habr.com/ru/post/fr426605/


All Articles