Utilisation de PHP_Code_Sniffer.

| Lorna Mitchell (Auteur) | Joris Crozier (Traducteur) |

Cet article est la traduction de Using PHP_Code_Sniffer de Lorna Mitchell et introduit l'outil PHP_code_sniffer qui vous aidera à formater vos codes selon les standards définis.

Commentez

- Titre : Utilisation de PHP_Code_Sniffer.

- Auteurs : Lorna Mitchell (Auteur), Joris Crozier (Traducteur)

- Parution : 17 décembre 2009

- Licence : Copyright ® 2009 Joris CROZIER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- I. Installation de PHPCS

- II. Utilisation de PHPCS

- III. Que trouve-t-on dans la machine ?

- IV. Etablir des règles

- V. Fonctions de «sniff» standards de PEAR

- VI. Réflexions finales sur les normes de codage

- VII. Liens

Introduction

PHP Code Sniffer (PHPCS) est un package pour la vérification de syntaxe, disponible à partir de PEAR. Il peut vérifier le code en suivant des règles définies couvrant tout ce qui est possible, depuis les blancs en passant par les commentaires phpdoc et les conventions de nommage de variables et au-delà. Dans cet article nous allons démarrer avec PHPCS, l'utilisant pour vérifier la syntaxe de nos fichiers, et aller plus loin pour examiner la façon dont les règles sont créées et les normes définies.

I - Installation de PHPCS

Il existe deux manières principales pour l'installation de PHPCS - directement, ou via PEAR. Utiliser les référentiels de PEAR est recommandé et s'adapte à toutes les plates-formes - il est aussi probablement très familier à tous les développeurs PHP ! L'alternative est d'utiliser la méthode disponible pour votre système - par exemple mon système Ubuntu comprend eu un paquet aptitude appelé php-codesniffer qui a installé cette fonctionnalité pour moi.

Pour utiliser la méthode de PEAR, vous devez simplement mettre à jour votre référentiel PEAR et puis tapez :

pear install PHP_CodeSniffer

Maintenant, nous avons le package installé, il est temps de jeter un oeil à ce qu'il peut faire pour nous.

II - Utilisation de PHPCS

PHPCS est un utilitaire de ligne de commande qui peut produire divers niveaux de détail et d'évaluer un fichier, un répertoire complet, ou un motif de configuration des fichiers cibles. Sa sortie est une liste de défauts trouvés, avec un message d'erreur et le numéro de ligne fourni. Par défaut, PHPCS est livré préinstallé avec un certain nombre de définitions du codage standard. Pour voir quelles définitions sont disponibles pour la vérification, utilisez le switch -i.

phpcs -i
Les normes de codage installées sont MySource, PEAR, Squiz, PHPCS et Zend

Cela montre un manquement aux normes de codage y compris la norme PHPCS, la norme de Zend (utilisée par le Zend Framework et de nombreux autres projets), et le standard bien connu de PEAR. Il est possible de mettre à profit et adapter ces normes existantes afin de cadrer avec les normes de codage utilisées par un projet particulier et ce sujet sera exploré plus tard dans l'article. Les normes ont des exigences différentes pour les normes du code et en tant que tel, nous pouvons évaluer un fichier simple avec un couple de normes différentes et voir certaines différences immédiates. Prenez l'exemple de code suivant :

<?php

class recipe
{

protected $_id;

public $name;

public $prep_time;

function getIngredients() {
$ingredients = Ingredients::fetchAllById($this->_id);
return $ingredients;
}
}
?>

Pour valider ce code de classe avec la norme Zend, on utilise la syntaxe suivante :

phpcs --standard=Zend recipe.class.php

FILE: /home/lorna/phpcs/recipe.class.php
--------------------------------------------------------------------------------
FOUND 3 ERROR(S) AND 0 WARNING(S) AFFECTING 3 LINE(S)
--------------------------------------------------------------------------------
10 | ERROR | Variable "prep_time" is not in valid camel caps format
13 | ERROR | Spaces must be used to indent lines; tabs are not allowed
17 | ERROR | A closing tag is not permitted at the end of a PHP file
--------------------------------------------------------------------------------

Toutefois, les normes Zend ne nécessitent pas certains éléments comme le font les autres normes, par exemple les normes de PEAR s'attendent à des noms de classes capitalisés et que les ouvertures des accolades des fonctions soient sur de nouvelles lignes, et cela devient évident si nous validons le même fichier recipe.class.php avec la norme de PEAR. Nous utilisons la même syntaxe que précédemment mais en changeant le commutateur standard de PEAR :

FILE: /home/lorna/phpcs/recipe.class.php
--------------------------------------------------------------------------------
FOUND 8 ERROR(S) AND 0 WARNING(S) AFFECTING 5 LINE(S)
--------------------------------------------------------------------------------
2 | ERROR | Missing file doc comment
3 | ERROR | Class name must begin with a capital letter
3 | ERROR | Missing class doc comment
6 | ERROR | Protected member variable "_id" must not be prefixed with an
|       | underscore
12 | ERROR | Missing function doc comment
12 | ERROR | Opening brace should be on a new line
13 | ERROR | Line indented incorrectly; expected at least 8 spaces, found 1
13 | ERROR | Spaces must be used to indent lines; tabs are not allowed
--------------------------------------------------------------------------------

On peut passer par notre classe et la modifier pour se conformer à ces normes, les modifications sont essentiellement sémantiques, mais surtout sur de grandes bases de codes, la cohérence et la connaissance sont des facteurs clés pour en faciliter la maintenance et la lisibilité par les développeurs. Il ya quelques petites choses faciles que nous pouvons fixer dans notre code pour que les normes PHPCS affichent moins d'avertissements - voici le fichier de classe mis à jour :

<?php

class Recipe
{

protected $id;

public $name;

public $prep_time;

function getIngredients()
{
$ingredients = Ingredients::fetchAllById($this->_id);
return $ingredients;
}
}
?>

Les petites corrections de la classe sont minimes, voire presque insignifiantes, un programmeur regardant une classe ou l'autre devrait y regarder à deux fois pour être en mesure de repérer les différences. La façon la plus claire pour comparer les fichiers, je pense, serait d'utiliser le fichier de différences entre les deux versions :

3c3
< class recipe
---
> class Recipe
6c6
<     protected $_id;
---
>     protected $id;
12,13c12,14
<     function getIngredients() {
<     $ingredients = Ingredients::fetchAllById($this->_id);
---
>     function getIngredients()
>     {
>         $ingredients = Ingredients::fetchAllById($this->_id);

Maintenant, si vous avez revérifié le fichier en fonction des normes de PEAR, vous pouvez voir que seules les observations manquantes PHPDocumentor sont répertoriées comme des problèmes avec le fichier :

FILE: /home/lorna/phpcs/recipe.class.php
--------------------------------------------------------------------------------
FOUND 3 ERROR(S) AND 0 WARNING(S) AFFECTING 3 LINE(S)
--------------------------------------------------------------------------------
2 | ERROR | Missing file doc comment
3 | ERROR | Missing class doc comment
12 | ERROR | Missing function doc comment
--------------------------------------------------------------------------------

Pour obtenir de notre code qu'il se valide correctement, on peut alors ajouter des commentaires attendus par PHPDocumentor - il existe des didacticiels autour de cet outil, mais le meilleur endroit pour commencer est sur leur page d'accueil à http://www.phpdoc.org/. Voici la classe avec les commentaires ajoutés :

<?php

/**
* Recipe class file
*
* PHP Version 5.2
*
* @category Recipe
* @package  Recipe
* @author   Lorna Jane Mitchell <lorna@ibuildings.com>
* @license  http://opensource.org/licenses/gpl-license.php GNU Public License
* @link     http://example.com/recipes
*/

/**
* Recipe class
*
* The class holding the root Recipe class definition
*
* @category Recipe
* @package  Recipe
* @author   Lorna Jane Mitchell <lorna@ibuildings.com>
* @license  http://opensource.org/licenses/gpl-license.php GNU Public License
* @link     http://example.com/recipes/recipe
*/
class Recipe
{

protected $id;

public $name;

public $prep_time;

/**
* Get the ingredients
*
* This function calls a static fetching method against the Ingredient class
* and returns everything matching this recipe ID
*
* @return array An array of Ingredient objects
*/
function getIngredients()
{
$ingredients = Ingredient::fetchAllByRecipe($this->id);
return $ingredients;
}
}
?>

III - Que trouve-t-on dans la machine ?

PHPCS fonctionne sur la base de «jetons de contenu» d'un fichier qu'il valide avec un ensemble donné de règles. L'étape divise le code PHP en une série de blocs de construction, et les règles sont en mesure de vérifier toutes sortes de choses à l'intérieur. Donc, si nous devions traiter la fonction que nous avons incluse au début de notre classe, nous obtiendrions quelque chose qui ressemble à ceci :

Array
(
[0] => Array
(
[0] => 367
[1] => <?php

[2] => 1
)

[1] => Array
(
[0] => 370
[1] =>

[2] => 2
)

[2] => Array
(
[0] => 333
[1] => function
[2] => 3
)

[3] => Array
(
[0] => 370
[1] =>
[2] => 3
)

[4] => Array
(
[0] => 307
[1] => getIngredients
[2] => 3
)

[5] => (
[6] => )
[7] => Array
(
[0] => 370
[1] =>
[2] => 3
)

[8] => {
[9] => Array
(
[0] => 370
[1] =>

[2] => 3
)

[10] => Array
(
[0] => 309
[1] => $ingredients
[2] => 4
)
... (tronqué)

Le résultat est tronqué en raison de la taille de celui-ci, même quand on l'examine avec print_r() plutôt que var_dump(). La conversion en jetons est en fait une fonctionnalité qui est disponible pour nous dans PHP lui-même - le get_all_tokens() qui accepte une chaîne. La fonction file_get_contents() a été utilisée pour récupérer le contenu du fichier comme argument de get_all_tokens() et ce que vous voyez ci-dessus est le résultat de la fonction print_r() du résultat.

Il y a une grande quantité d'informations ici, ce qui n'est pas vraiment lisible pour nous (180 lignes sont générées au total, à partir d'une fonction de 2 lignes) - mais nous pouvons traiter les sorties et les vérifier grâce à PHPCS et avec un ensemble de règles.

IV - Etablir des règles

Pour comprendre comment nous pouvons créer nos propres définitions des normes, jetons un oeil sur les normes existantes qui sont fournies avec PHPCS - les emplacements de celles-ci diffèrent entre les plates-formes, selon l'endroit où les met PEAR. Pour moi, sur une installation Ubuntu, elles sont dans /usr/share/php/PHP/CodeSniffer/Standards. Toutes les normes étendent la classe de base du fichier CodingStandard.php dans ce répertoire. Ceci définit deux méthodes simples : getIncludedSniffs() et getExcludedSniffs(). Elles nous permettent d'utiliser les définitions standards et simplifient l'ajout et la suppression des normes personnelles pour faire un standard à notre image.

Ces «sniffs» sont des règles atomiques couvrant tout, de la longueur de ligne au nommage des variables en passant par des tâches de «reniflement de code» comme le code inaccessible ou les boucles mal mises en forme. Chaque norme de codage a son propre répertoire de «sniffs» et tout ce qui y est inclus fait automatiquement partie de la norme. Toutefois, chaque norme peut également s'appuyer sur les «sniffs» d'autres normes, et il y a un grand ensemble de «sniffs de démarrage», utilisés dans la plupart des normes, qui sont inclus avec PHPCS dans le répertoire générique.

Pour avoir un aperçu des «sniffs» et la façon dont ils héritent, jetons un oeil à une véritable norme - La norme de PEAR est dans le répertoire PEAR et le fichier de classe est PEARCodingStandard.php (cette extension n'est rien si elle n'est pas méthodique !). La classe ressemble à ceci :

<?php
/**
* PEAR Coding Standard.
*
* PHP version 5
*
* @category  PHP
* @package   PHP_CodeSniffer
* @author    Greg Sherwood <gsherwood@squiz.net>
* @author    Marc McIntyre <mmcintyre@squiz.net>
* @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
* @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* @version   CVS: $Id: PEARCodingStandard.php,v 1.6 2007/08/02 23:18:31 squiz Exp $
* @link      http://pear.php.net/package/PHP_CodeSniffer
*/

if (class_exists('PHP_CodeSniffer_Standards_CodingStandard', true) === false) {
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_CodingStandard not found');
}

/**
* PEAR Coding Standard.
*
* @category  PHP
* @package   PHP_CodeSniffer
* @author    Greg Sherwood <gsherwood@squiz.net>
* @author    Marc McIntyre <mmcintyre@squiz.net>
* @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
* @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* @version   Release: 1.1.0
* @link      http://pear.php.net/package/PHP_CodeSniffer
*/
class PHP_CodeSniffer_Standards_PEAR_PEARCodingStandard extends PHP_CodeSniffer_Standards_CodingStandard
{

/**
* Return a list of external sniffs to include with this standard.
*
* The PEAR standard uses some generic sniffs.
*
* @return array
*/
public function getIncludedSniffs()
{
return array(
'Generic/Sniffs/Formatting/MultipleStatementAlignmentSniff.php',
'Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php',
'Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php',
'Generic/Sniffs/PHP/LowerCaseConstantSniff.php',
'Generic/Sniffs/PHP/DisallowShortOpenTagSniff.php',
'Generic/Sniffs/WhiteSpace/DisallowTabIndentSniff.php',
);

}//end getIncludedSniffs()

}//end class
?>

Cette classe montre qu'il y a quelques «sniffs» inclus à partir du répertoire générique ainsi que ceux spécifiques à cette norme. En regardant les «sniffs» pour PEAR on voit qu'ils sont les suivants :

./Classes:
ClassDeclarationSniff.php

./Commenting:
ClassCommentSniff.php  FileCommentSniff.php  FunctionCommentSniff.php  InlineCommentSniff.php

./ControlStructures:
ControlSignatureSniff.php  InlineControlStructureSniff.php

./Files:
IncludingFileSniff.php  LineEndingsSniff.php  LineLengthSniff.php

./Functions:
FunctionCallArgumentSpacingSniff.php  FunctionCallSignatureSniff.php  ValidDefaultValueSniff.php

./NamingConventions:
ValidClassNameSniff.php  ValidFunctionNameSniff.php  ValidVariableNameSniff.php

./WhiteSpace:
ScopeClosingBraceSniff.php  ScopeIndentSniff.php

Ils viennent s'ajouter aux «sniffs» génériques inclus que nous avons précédemment énumérés. Il y a beaucoup de détails de ces différents standards et je vous recommande fortement de jeter un oeil dessus car tous les couvrir ferait un très gros article. Nous allons jeter un oeil du côté des fonctions «sniffs» utilisées par PEAR bien que ces normes sont bien connues et faciles à comprendre.

V - Fonctions de «sniff» standards de PEAR

La classe PEARCodingStandard comprend les «sniffs» Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php. Un bref regard dans le répertoire Generic/Sniffs/Functions révèle qu'il y a aussi un fichier standard appelé OpeningFunctionBraceKernighanRitchieSniff.php Il s'agit d'un petit bout d'histoire de l'informatique, il y a deux écoles de pensée sur l'endroit où l'accolade ouvrante doit aller quand une fonction est déclarée. Brian Kernighan et Dennis Ritchie (qui ont inventé respectivement Unix et C ) ont préconisé de la poser sur la même ligne que la déclaration de fonction tandis que le style BSD, défendu par Eric Allman (créateur de sendmail), va sur la ligne suivante. PEAR utilise le style nouvelle ligne et c'est pourquoi le OpeningFunctionBraceBsdAllmanSniff est utilisé dans le standard de PEAR.

Maintenant, nous avons fini notre leçon d'histoire, nous allons jeter un oeil à ce «sniffs». Tous les «sniffs» prennent deux arguments - l'un avec tous les jetons du fichier, et l'autre indiquant où dans le jeton de la pile est déclenché l'appel de fonction.

<?php
/**
* Generic_Sniffs_Methods_OpeningMethodBraceBsdAllmanSniff.
*
* PHP version 5
*
* @category  PHP
* @package   PHP_CodeSniffer
* @author    Greg Sherwood <gsherwood@squiz.net>
* @author    Marc McIntyre <mmcintyre@squiz.net>
* @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
* @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* @version   CVS: $Id: OpeningFunctionBraceBsdAllmanSniff.php,v 1.8 2008/05/05 03:59:12 squiz Exp $
* @link      http://pear.php.net/package/PHP_CodeSniffer
*/

/**
* Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff.
*
* Checks that the opening brace of a function is on the line after the
* function declaration.
*
* @category  PHP
* @package   PHP_CodeSniffer
* @author    Greg Sherwood <gsherwood@squiz.net>
* @author    Marc McIntyre <mmcintyre@squiz.net>
* @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600)
* @license   http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence
* @version   Release: 1.1.0
* @link      http://pear.php.net/package/PHP_CodeSniffer
*/
class Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff implements PHP_CodeSniffer_Sniff
{

/**
* Registers the tokens that this sniff wants to listen for.
*
* @return void
*/
public function register()
{
return array(T_FUNCTION);

}//end register()

/**
* Processes this test, when one of its tokens is encountered.
*
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int                  $stackPtr  The position of the current token in the
*                                        stack passed in $tokens.
*
* @return void
*/
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

if (isset($tokens[$stackPtr]['scope_opener']) === false) {
return;
}

$openingBrace = $tokens[$stackPtr]['scope_opener'];

// The end of the function occurs at the end of the argument list. Its
// like this because some people like to break long function declarations
// over multiple lines.
$functionLine = $tokens[$tokens[$stackPtr]['parenthesis_closer']]['line'];
$braceLine    = $tokens[$openingBrace]['line'];

$lineDifference = ($braceLine - $functionLine);

if ($lineDifference === 0) {
$error = 'Opening brace should be on a new line';
$phpcsFile->addError($error, $openingBrace);
return;
}

if ($lineDifference > 1) {
$ender = 'line';
if (($lineDifference - 1) !== 1) {
$ender .= 's';
}

$error = 'Opening brace should be on the line after the declaration; found '.($lineDifference - 1).' blank '.$ender;
$phpcsFile->addError($error, $openingBrace);
return;
}

// We need to actually find the first piece of content on this line,
// as if this is a method with tokens before it (public, static etc)
// or an if with an else before it, then we need to start the scope
// checking from there, rather than the current token.
$lineStart = $stackPtr;
while (($lineStart = $phpcsFile->findPrevious(array(T_WHITESPACE), ($lineStart - 1), null, false)) !== false) {
if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) {
break;
}
}

// We found a new line, now go forward and find the first non-whitespace
// token.
$lineStart = $phpcsFile->findNext(array(T_WHITESPACE), $lineStart, null, true);

// The opening brace is on the correct line, now it needs to be
// checked to be correctly indented.
$startColumn = $tokens[$lineStart]['column'];
$braceIndent = $tokens[$openingBrace]['column'];

if ($braceIndent !== $startColumn) {
$error = 'Opening brace indented incorrectly; expected '.($startColumn - 1).' spaces, found '.($braceIndent - 1);
$phpcsFile->addError($error, $openingBrace);
}

}//end process()

}//end class

?>

Vous pouvez lire tout ou peu du code ci-dessus comme bon vous semble - personnellement, je pense que c'est un bel exemple que de vérifier la position des accolades, le formatage des messages d'erreur, et de vérifier également si l'indentation est correcte puisque nous avons compris où tout se situe de toute manière. Ce «sniffs» gère la déclaration de fonctions réparties sur plusieurs lignes et vérifie également que l'accolade est sur la même ligne plutôt que de simplement vérifier qu'elle existe au bout de quelques espaces. Cela fait certainement quelques vérifications approfondies, et beaucoup plus facilement et plus rapidement que nous serions en mesure de le faire à la main.

Tant que nous sommes sur le sujet de la structure de la fonction, examinons également les «sniffs» PEAR spécifiques pour les fonctions. Ce sont :

- FunctionCallArgumentSpacingSniff.php

- FunctionCallSignatureSniff.php

- ValidDefaultValueSniff.php

Pouvez-vous deviner ce que chacun des «sniffs» fait ? Ils sont de bons exemples d'ajouts dans la continuité de votre code et je ne vais pas reproduire leur contenu réel ici, mais je recommanderais certainement d'y jeter un oeil, surtout si vous envisagez l'écriture de vos propres normes. Ils peuvent chacun appliquer certains éléments de bonnes pratiques lors de la déclaration des fonctions, et vous avertir lorsque des arguments sans valeurs par défaut sont placés après ceux qui ont des valeurs par défaut, par exemple. Ils étudient également l'espacement autour des crochets et les arguments avancés dans les déclarations de fonctions (PEAR ne permet pas d'espaces autour de crochets, mais les oblige entre les arguments, après la virgule). La continuité de ce genre fait que votre code est facile à lire parce que votre cerveau attend son énoncé d''une certaine manière.

VI - Réflexions finales sur les normes de codage

Les normes de codage peuvent être considérées comme chronophages, un autre morceau de paperasse inventé par les entreprises pour qu'un développeur livre tout ce qu'il peut. Tenter de coder dans une norme inconnue sans le soutien d'un outil tel que PHPCS est difficile à réaliser, si c'est encore possible. Parce que je travaille avec des bases de codes différents, appartenant à des clients, à des projets open source, ou à moi, je vois beaucoup de normes différentes et dois coder dans un certain nombre de normes parfois dans une seule journée ! Je ne peux pas tenir le détail dans ma tête, mais un outil comme PHPCS veut dire que je suis toujours le code de production qui est utile pour mes collaborateurs sur un projet, qu'ils soient clients, amis ou moi-même et que je peux coder sans vraiment trop avoir à réfléchir à ce sujet une fois que j'ai pris le temps de mettre cet outil en place.

PHPCS est plus utile quand il est indolore, est intégré à notre cycle de développement et outils existants. Il existe des intégrations pour cet outil dans la plupart des éditeurs et il peut facilement être ajouté comme un crochet de SVN ou comme une étape dans votre processus d'intégration continue. Ayant cet outil au niveau le plus bas possible, il est plus probable que, en tant que développeurs, nous allons faire usage de ses fonctions tout au long de nos projets.

Ce module est disponible par le biais de PEAR et je tiens à remercier les auteurs de toutes les extensions qui travaillent si dur pour rendre leur code disponible et le maintenir pour les autres - des remerciements spéciaux à Greg Sherwood, qui est le responsable du package PHP_CodeSniffer de PEAR.

VII - Liens

Vous pouvez aussi aller voir mes autres traductions.

Un grand merci à jacques_jean et Watcher pour leurs relectures orthographique.