qualité de code avec php-cs-fixer et php mess detector (+ docker !)

php-cs-fixer est un outil développé par sensio labs, qui relève les erreurs liées notamment au psr2.

php mess detector est un outil  permettant de vérifier que des variables inutilisées ne trainent pas, le code mort, dupliqué etc.

J’ai récemment dû mettre en place un script lançant les deux outils et bloquant un push git, tout cela avec docker.

Tout commence par un DockerFile

FROM php:7-apache

RUN apt-get update && apt-get install -y php-pear git wget libbz2-dev

RUN curl -L https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v1.11.6/php-cs-fixer.phar -o php-cs-fixer && \
    chmod a+x php-cs-fixer && \
    mv php-cs-fixer /usr/local/bin/php-cs-fixer

RUN wget -c http://static.phpmd.org/php/latest/phpmd.phar && \
    chmod a+x phpmd.phar && \
    mv phpmd.phar /usr/local/bin/phpmd

RUN wget http://static.pdepend.org/php/latest/pdepend.phar && \
    chmod a+x pdepend.phar && \
    mv pdepend.phar /usr/local/bin/pdepend

RUN docker-php-ext-install bz2

COPY scripts/qa.sh /qa.sh
COPY config/.php_cs /.php_cs
COPY config/rulesets /rulesets

WORKDIR /var/www/project

Une fois les libs récupérées, je les déplace dans /usr/local/bin. J’installe l’extension bz2 pour utiliser les phar.

Enfin, je copie le script qa.sh et les fichiers de configuration config/.php_cs et config/rulesets à la racine du container.

Le fichier .php_cs

Ce fichier est indispensable pour php-cs-fixer si vous souhaitez exclure des répertoires lors de l’analyse (malheureusement, il n’existe pas d’option pour cela, c’est bien dommage).

<?php

$finder = Symfony\CS\Finder\DefaultFinder::create()
    ->files()
    ->exclude(['tests/_support', 'tests/_data', 'tests/_output'])
    ->in('src/AppBundle')
    ->notPath('tests/_bootstrap.php')
;

return Symfony\CS\Config\Config::create()
    ->finder($finder)
;

?>

Je demande donc à exclure les répertoires tests/_support, tests/_data, tests/_output, et à exclure un fichier en particulier, test/_bootstrap.php

Les rulesets de php mess detector

Les règles de mess detector sont très bien, mais certaines sont… un peu casse-pieds. Par exemple, si une variable a moins de deux caractères, il ne va pas être très content (dommage pour les variables comme $em, $id, $qb et j’en passe). De même, mess detector considère qu’utiliser else dans son code, C’EST LE MAL!!! 😉

Certaines règles, trop lourdes, ont donc été retirées dans ces fichiers. J’ai recréé deux fichiers de mess detector, cleancode.xml et naming.xml, et j’ai changé une configuration (les noms de variables peuvent faire 30 caractères maximum au lieu de 20), et ai retiré les règles concernant le else et les variables dont le nom était trop court. J’aurais pu dire que je n’acceptais pas les variables de moins de quatre caractères, mais cette règle me paraissait vraiment trop strict au final.

cleancode.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Naming Rules"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
    <description>
        The Naming Ruleset contains a collection of rules about names - too long, too short, and so forth.
    </description>

    <rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>

</ruleset>

 

naming.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Naming Rules"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
    <description>
        The Naming Ruleset contains a collection of rules about names - too long, too short, and so forth.
    </description>

    <rule ref="rulesets/naming.xml/LongVariable">
	<properties>
            <property name="maximum" value="30" />
        </properties>
    </rule>
    <rule ref="rulesets/naming.xml/ShortMethodName"/>
    <rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
    <rule ref="rulesets/naming.xml/BooleanGetMethodName"/>

</ruleset>

 

On y est presque. Il reste à créer le fichier qa.sh qui permet de lancer php-cs-fixer, php mess detector, ou les deux:

#!/usr/bin/env bash

function showHelp
{
    echo "Help:"
    echo " -b      Switch to the branch indicated"
    echo " -f      Launch php-cs-fixer"
    echo " -m      Launch phpmd"
    echo " -u      host url"
    echo " -h      Display this help"
}

RED='\e[31m'
GREEN='\e[32m'
YELLOW='\e[33m'
NOCOLOR='\e[0m'

branch=""
phpCsFixer=false
phpCsFixerError=false
phpmd=false
phpmdError=false
hostUrl="path/to/quality_check/results"

while getopts b:fmu:h opts; do
   case ${opts} in
      b ) branch=${OPTARG} ;;
      f ) phpCsFixer=true ;;
      m ) phpmd=true ;;
      u ) hostUrl=${OPTARG} ;;
      h ) showHelp
          exit ;;
   esac
done

echo -e "${YELLOW}QA check: With Great Power Comes Great Responsibility..."

if [[ $branch == "" ]]; then
   branch=$(git rev-parse --abbrev-ref HEAD)
fi

cd /var/www/project

git fetch --all -p --tags >/dev/null 2>&1
git checkout ${branch}
echo -e "${NOCOLOR}"

if [ $phpCsFixer = true ]; then
    echo -e "${YELLOW}Running php-cs-fixer in fix mode"
    php-cs-fixer fix src/AppBundle --level=symfony --config-file=/.php_cs

    modifications=$(git diff --name-only | wc -l)

    if [[ ${modifications} > 0 ]]; then
       echo -e "${RED}[ERROR] php-cs-fixer has detected bad quality code: please check modified files !"
       phpCsFixerError=true
    fi
fi

echo -e "${NOCOLOR}"

# PHP mess detector
if [ $phpmd = true ]; then
    echo -e "${YELLOW}Running php mess detector"

    timestamp=$(date +%s)
    lastCommit=$(git rev-parse HEAD)
    currentRepoName=$(git config --local remote.origin.url|sed -n 's#.*/\([^.]*\)\.git#\1#p')
    reportFileName=phpmd-${timestamp}-${lastCommit}.html
    reportFilePath=/var/www/quality_check/results/${currentRepoName}/${reportFileName}
    mkdir -p /var/www/quality_check/results/${currentRepoName}

    phpmd src/AppBundle/ html codesize,controversial,design,unusedcode,/rulesets/cleancode.xml,/rulesets/naming.xml --reportfile ${reportFilePath} --exclude tests

    if [ $? -gt 0 ]; then
       echo -e "${RED}[ERROR] php mess detector has identified some issues in your code: please check ${hostUrl}/${currentRepoName}/${reportFileName}";
       phpmdError=true
    fi

    # Remove useless file created by php-md
    rm -f 0
fi

echo -e "${NOCOLOR}"

# Fail push if errors
if [[ $phpCsFixerError == true || $phpmdError == true ]]; then
               cat << "EOF"

     __ __   ___   __ __       _____ __ __   ____  _      _
    |  |  | /   \ |  |  |     / ___/|  |  | /    || |    | |
    |  |  ||     ||  |  |    (   \_ |  |  ||  o  || |    | |
    |  ~  ||  O  ||  |  |     \__  ||  _  ||     || |___ | |___
    |___, ||     ||  :  |     /  \ ||  |  ||  _  ||     ||     |
    |     ||     ||     |     \    ||  |  ||  |  ||     ||     |
    |____/  \___/  \__,_|      \___||__|__||__|__||_____||_____|

                     .MM
                     MM.         M   .?MMM.
                    .M.        .MM   .M M
                   .M7       .MMM.      .M
                   MM        MMM        .M
                  .M        MMMM         D:
                 :M.      .MMMMMMM,.      M
                MMO   ..MMMMMMMMDMMMM.    MM
                .MMMM      OMMMM       MMMMM
                  .MMMMMMNMMM. MMMMMMMMMMM M
                   MMMMMMMMM.  ~MMMMMMMMM+ .M
                  .MMMMMMMMM   .MMMMMMMMM,  M
                  MMMMMMMM.M.  :M.MMMMMMM.  .M
                  MMMMMMM MM  ..M  .MMMMM.   M.
                  MMMMMM..MMM.+MM    MMMM.   .M
                  MMMMM. .MMMMMMM    .MMM     M.
                  +MMM.. MMMMMMMM,    MMM      M
                  .MM.   MMMMMMMMM     MM.     M.
                   M.    MMMMMMMMM.    .Z      .M
                        MMMMMMMMMM      .       M.
                        MMMMMMMMMMM.            .
                       :MMMMMMMMMMM.
                       MMMMMMMMMMMMM
                        ...I8NO?.....
                         ...   .=~.
     ____    ___   ______      ____   ____  _____ _____
    |    \  /   \ |      |    |    \ /    |/ ___// ___/
    |  _  ||     ||      |    |  o  )  o  (   \_(   \_
    |  |  ||  O  ||_|  |_|    |   _/|     |\__  |\__  |
    |  |  ||     |  |  |      |  |  |  _  |/  \ |/  \ |
    |  |  ||     |  |  |      |  |  |  |  |\    |\    |
    |__|__| \___/   |__|      |__|  |__|__| \___| \___|


EOF
    exit 1
else
    echo -e "${GREEN}[SUCCESS] qa check succeeded. Well done ;-)"
fi

exit 0

C’est un gros fichier mais il est aisément compréhensible:

  • Il est possible de définir l’argument « branch » pour se placer sur une branche en particulier et effectuer les tests
  • Avec l’option -f , php-cs-fixer s’exécute
  • Avec l’option -m, php mess detector s’exécute
  • Quand php-cs-fixer s’exécute, il corrige les fichiers et détecte s’il y a eu des modifications pour lancer une erreur à la fin. Il s’exécute avec le niveau « symfony » et prend en compte le fichier de configuration que nous avons renseigné précédemment.
  • Mess detector s’exécute, prend tous les standards possibles sauf naming et cleancode, car nous lui demandons de prendre les nôtres: codesize,controversial,design,unusedcode,/rulesets/cleancode.xml,/rulesets/naming.xml
  • Mess detector génère un 1 ou un 0 selon que des erreurs aient été detectées ou non. Le résultat est versionné et placé dans le répertoire du même nom que le repot sur lequel vous vous trouvez.

Plus que deux étapes:

  • soit on créé le fichier .git/hooks/pre-push et l’on y place, tout à la fin, le code suivant (en retirant la dernière ligne existante exit 0):
    docker run -v $PWD:/var/www/project -v $PWD/../../quality_check:/var/www/quality_check quality_check bash -c "/qa.sh -f -m"
    responseCode=$?;
    exit $responseCode;
  • soit on fait appel au container en ligne de commande:
    docker run -v $PWD:/var/www/project -v $PWD/../../quality_check:/var/www/quality_check quality_check bash -c "/qa.sh -f -m"

     

     

Bonus:

Si vous êtes sur gitlab et que vous souhaitez automatiser les builds, il suffit de créer le fichier .gitlab-ci.yml et d’y placer le code suivant:

qa:
    variables:
       HOST_URL: http://my-url-server
    script:
        - docker run -v $CI_PROJECT_DIR:/var/www/project -v /home/admin/docker/quality_check/results:/var/www/quality_check/results quality_check bash -c "/qa.sh -m -u $HOST_URL"

Bien évidemment, si vous connaissez le principe de gitlab-ci.yml, vous savez qu’il faut aussi créer un runner shell pour que cela fonctionne. Mais honnêtement, ça vaut le coup. Ca ne bloquera pas le push sur le serveur (sauf si vous avez la main et que vous pouvez modifier le hook pre-receive directement sur votre serveur) mais ça exécutera un build qui passera ou échouera et vous pourrez alors vous envoyer une alerte si tel est le cas.

Rédigé par

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.