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.