Déboguez vos codes PHP

Magazine
Marque
GNU/Linux Magazine
Numéro
243
Mois de parution
décembre 2020
Spécialité(s)


Résumé

La mise au point de programmes PHP est parfois perçue comme archaïque, car la configuration d’un environnement efficace de debugging peut s’avérer déroutante. Voici comment paramétrer une confortable installation pour profiter pleinement d’outils professionnels et maîtriser le développement local ou distant.


Body

PHP est un langage qui a considérablement évolué depuis sa naissance il y a plus de 25 ans, tant dans sa philosophie que dans l’écosystème de travail. Si la recherche de problèmes dans un programme PHP a longtemps été synonyme d’affichage de var_dump() et d’interminables essais par tâtonnement, les IDE (Integrated Development Environment) disposent aujourd’hui de tout le nécessaire pour effectuer du pas à pas, poser des points d’arrêt conditionnels, examiner et modifier des variables en cours d’exécution et même intercepter des requêtes choisies sur des applicatifs déployés.

1. Extensions de débogage

Le langage, conçu dans un esprit de scripting, ne prévoyait pas à l’origine de moyens de mise au point. Le debugging nécessite une extension, à l’instar de beaucoup d’autres fonctionnalités modulaires pour PHP.

Il existe deux principales extensions de débogage : Zend et Xdebug. La première étant propriétaire et livrée exclusivement sous la forme d’un binaire, nous l’écarterons ici pour nous concentrer sur la seconde. D’autres tentatives de modules de débogage ont vu le jour au fil des années, mais elles n’ont pas survécu. Proposé et maintenu par Derick Rethans, l’auteur du désassembleur VLD [3], le projet Xdebug [1] est très actif et s’est imposé par sa grande qualité. Tous les principaux IDE le prennent en charge.

2. Utilisation de Xdebug

Xdebug fonctionne en mode client-serveur, où le programme PHP en cours d’exécution joue le rôle du client, et l’IDE est le serveur. Lorsque l’extension est installée et activée, le moteur PHP peut initier une connexion vers votre environnement de développement pour démarrer une session de débogage. Les deux parties communiquent en réseau, selon un protocole ouvert dénommé DBGp [2]. Plusieurs configurations sont disponibles pour l’établissement de la connexion pour couvrir tous les cas d’utilisation, ainsi que nous le verrons dans la suite.

Une fois la session démarrée, le client (le processus PHP qui exécute le script à examiner) obtient de la part du serveur (l’IDE) la liste des points d’arrêt et autres informations de mise au point. L’interaction commence alors ; l’environnement de développement peut exécuter des instructions pas à pas, inspecter types et contenus de variables, consulter et remonter la pile d’appels, le tout au moyen de discussions selon le protocole DBGp. À la fin de l’exécution du script (ou sur demande manuelle du développeur), la session se termine. Une autre connexion sera initiée lors d’une nouvelle exécution de script par le moteur PHP.

Client-serveur

Vous remarquerez en lisant la documentation officielle que les termes « client » et « serveur » y sont inversés, et désignent respectivement l’IDE et le moteur PHP. Cette terminologie me semble avoir été choisie pour s’accorder avec le schéma web selon lequel le moteur PHP joue le rôle de serveur et le poste de travail, celui de client. Mais, contrairement à d’autres langages pour lesquels le runtime est effectivement en écoute de connexions entrantes de débogage, le cas de PHP est différent et c’est bien l’IDE qui se place en écoute, et le serveur PHP qui initie, le cas échéant, une connexion vers le serveur de débogage qui se trouve être la station de travail.

2.1 Installation et activation

Plusieurs méthodes existent. Sur Debian Buster, l’extension php-xdebug peut s’installer directement par :

$ sudo apt-get install -y php-xdebug

On vérifie aussitôt par :

$ php -vPHP 7.3.19-1~deb10u1 (cli) (built: Jul 5 2020 06:46:45) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.19, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.19-1~deb10u1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.7.0RC2, Copyright (c) 2002-2019, by Derick Rethans

Si cette méthode ne fonctionne pas pour vous, Xdebug est livré sous la forme d’un module PECL. Votre système doit posséder les dépendances nécessaires pour la compilation d’extensions PHP, que vous pouvez installer grâce au paquet php-dev (Debian) ou php-devel (CentOS) :

$ sudo apt-get install -y php-dev

Vous disposerez ensuite de la commande pecl, et vous pourrez installer Xdebug de la façon suivante :

$ sudo pecl install xdebug

Ceci a pour effet de démarrer le téléchargement des sources et la compilation du module.

Une fois la construction terminée, on obtient le message suivant :

Build process completed successfully
Installing '/usr/lib/php/20180731/xdebug.so'
install ok: channel://pecl.php.net/xdebug-2.9.6
configuration option "php_ini" is not set to php.ini location
You should add "zend_extension=/usr/lib/php/20180731/xdebug.so" to php.ini

Il convient donc de suivre l’indication. Créez un fichier xdebug.ini comprenant la directive :

zend_extension=xdebug.so

Il s’agit d’une zend_extension, en raison de son cycle de vie particulier [4] auprès du moteur PHP. Mais ceci n’a pas d’importance pour nous.

Il est inutile de préfixer le chemin complet du module xdebug.so, car PHP 7+ le déduit tout seul en fonction de ses paramètres de compilation.

Nous augmenterons le fichier xdebug.ini avec plus de directives dans la section 2.2 « Configuration ».

Placez ensuite ce fichier dans le répertoire de configuration de votre installation PHP, que vous pouvez identifier par la commande php -i en cherchant l’indication suivante :

$ php -i | grep ScanScan this dir for additional .ini files => /etc/php/7.3/cli/conf.d

Attention toutefois à ne pas oublier de placer également ce fichier si nécessaire dans les autres répertoires de configuration pour d’autres SAPI : la commande ci-dessus renvoie les informations pour CLI, elles seront différentes pour le module Apache notamment. Il s’agira généralement de liens symboliques vers des fichiers d’un répertoire /etc/php/7.3/mods-available ou similaire, selon les distributions. Il faudra ensuite penser à redémarrer ou recharger le service.

2.2 Configuration

Dès lors que l’extension Xdebug est chargée au niveau ini, le module est actif et est capable d’initier les connexions vers le terminal d’écoute, conformément aux paramètres que nous allons étudier maintenant.

Il est important de noter que les performances générales de PHP sont sensiblement dégradées lorsque Xdebug est activé, et il convient donc de réserver cette extension à un environnement de développement. En outre, un debugger installé sur un système en production peut constituer une porte d’attaque, comme nous allons le voir.

Voici les principales directives que nous allons détailler :

  • xdebug.remote_enable ;
  • xdebug.remote_port ;
  • xdebug.remote_connect_back ;
  • xdebug.remote_host ;
  • xdebug.remote_mode ;
  • xdebug.remote_autostart.

Ces paramètres permettent de contrôler trois situations bien différentes. Mais avant de les étudier, allumons l’aptitude de debugging par xdebug.remote_enable = true. Tant que ce paramètre n’est pas ouvert, l’extension est certes chargée par le moteur, mais elle n’est pas mise en œuvre (le serveur PHP n’essaie de contacter aucun terminal de débogage). Sur un système en production, il faudrait bien sûr veiller à ce qu’elle soit éteinte (ou mieux : que l’extension ne soit pas installée).

Nous allumons également pour le moment xdebug.remote_autostart = true, que nous aborderons dans la section 2.2.4 « Filtre ».

Gardez à l’esprit que, pour qu’elle soit prise en compte, toute modification de configuration ini nécessite le redémarrage / rechargement du service web.

2.2.1 Intercepter ses propres requêtes

Le premier cas est celui du débogage d’une requête sur le serveur de travail (local ou distant).

On place son IDE en écoute sur un port qu’on spécifie dans le paramètre xdebug.remote_port dans la configuration du serveur (par défaut 9000), on pointe le navigateur vers l’URL à attraper, et on attend que le serveur PHP tape à la porte de l’IDE en retour. Pour ce faire, le module Xdebug doit savoir que l’outil de débogage se trouve à la même adresse que l’origine de la requête HTTP : on configure donc xdebug.remote_connect_back = true.

Si le serveur web tourne en local, Xdebug n’aura pas de problème à atteindre le port ouvert par l’IDE. Mais il peut aussi être hébergé à l’extérieur et dans ce cas, l’hôte à contacter automatiquement par remote_connect_back est l’adresse IP de votre box internet (pensez à configurer le Port Forwarding sur votre routeur). Ce mode automatique résout le problème d’une adresse IP changeante, et permet également à plusieurs développeurs de faire des sessions de débogage sur ce même serveur distant, chacun depuis sa propre adresse (voir figure 1).

v-hs-php-debug figure 01

Fig. 1 : Le mode Remote Connect Back permet d’initier des sessions de débogage à l’adresse de départ de la requête HTTP.

2.2.2 Requêtes d’un visiteur tiers

Il n’est pas toujours possible d’initier soi-même la requête qu’on souhaite déboguer ni de la lancer depuis le poste de l’IDE. C’est par exemple le cas si le scénario à examiner ne se déroule que sur un appareil mobile, ou encore si on doit attraper un point d’arrêt dans la visite d’un utilisateur dont on ne sait pas reproduire soi-même le problème rencontré.

Dans ce cas, Xdebug doit initier une connexion non pas vers l’adresse automatique du navigateur de la visite, mais vers l’adresse IP (fixe) de l’IDE, comme indiqué en figure 2. Ceci se configure grâce à xdebug.remote_host = <1.2.3.4> (en remettant bien sûr xdebug.remote_connect_back = false).

v-hs-php-debug figure 02

Fig. 2 : Le mode Remote Host fixe permet d’aiguiller toutes les sessions de débogage sur le même poste de développement, quel que soit le point de départ de la requête HTTP.

2.2.3 Mode de déclenchement

Par défaut, lorsque Xdebug est activé, toute requête HTTP entrante provoquera une tentative de connexion vers le serveur de débogage (sans échec si connexion impossible). Dès la connexion établie, l’IDE indique au moteur PHP où se trouvent les points d’arrêt souhaités. Le script invoqué se déroule normalement jusqu’au premier point d’arrêt, à l’occasion duquel le module Xdebug contacte de nouveau l’IDE pour prise en main interactive.

Ce mode de déclenchement est celui par défaut, et correspond au réglage xdebug.remote_mode = req. Toute requête démarre automatiquement une session DBGp.

Mais parfois le scénario ne devient intéressant que lorsque le programme rencontre une erreur (incompatibilité de types, constante non définie, dépassement d’index…) ou lève une exception. Dans ce cas, il n’est pas possible de savoir à l’avance où placer un breakpoint. C’est le rôle du mode xdebug.remote_mode = jit (Just In Time), dans lequel Xdebug ne démarre une session qu’à la survenance de l’erreur, et directement en point d’arrêt sur le lieu de l’accident.

Il existe une troisième façon de déclencher une session de pas à pas : c’est l’arrêt manuel, par un appel à la fonction xdebug_break() en dur dans le code. Cette méthode, qui peut sembler contre-intuitive, s’avère parfois pratique notamment quand il faut intercepter l’exécution dans du code généré (par Smarty par exemple).

2.2.4 Filtre de déclenchement

Nous avons vu pour le moment comment initier systématiquement une interaction de débogage, que ce soit dès le démarrage du script par le moteur, ou à la survenance d’une exception.

Pourtant, ce mécanisme n’est pas toujours souhaité : nous voudrions pouvoir décider quand débuguer et quand laisser le serveur tranquille, même sans modifier les paramètres ini (et donc sans devoir redémarrer le service).

Ceci peut se faire au moyen du réglage xdebug.remote_autostart, que nous avions allumé à true auparavant, et que nous pouvons maintenant passer à false.

Dans ce mode, une requête entrante ne déclenchera une connexion que si le navigateur se trouve au sein d’une Xdebug Session (à ne pas confondre avec la session PHP). Pour commencer cette session, nous passons le paramètre de requête (sur l’URL ou en POST) : XDEBUG_SESSION_START=<nomQuelconque>.

Si ce paramètre est présent, le module Xdebug initiera la communication avec l’IDE, et va également émettre pour le navigateur un cookie XDEBUG_SESSION, de façon à ce que toutes les requêtes suivantes soient également interceptées, tant que la session Xdebug perdure. Elle s’arrête si l’on supprime le cookie, ou s’il expire seul (par défaut une heure, réglable [5]), ou si l’on passe explicitement un autre paramètre de requête nommé XDEBUG_SESSION_STOP.

2.3 Un mot sur la sécurité

Il est important d’insister encore sur le mode de fonctionnement du debugger en PHP : c’est le moteur (serveur Apache, etc.) qui lance une connexion vers un poste de débogage.

Si Xdebug est activé sur un serveur, et configuré en mode Remote Connect Back, tout attaquant qui envoie une requête HTTP pourra espérer recevoir en retour une connexion entrante sur un port de debugging (pas spécialement difficile à deviner ou à sniffer). Or, il n’est pas nécessaire de disposer des fichiers sources pour examiner en détail un script PHP distant : une fois le debugger connecté et à l’arrêt sur le démarrage du script, le terminal DBGp peut scruter toutes les variables du scope en cours, connaître le chemin distant complet de tous les fichiers inclus, et même exécuter des instructions PHP arbitraires à distance sur le serveur.

En conséquence, il est important de bien configurer la machine Xdebug, et de ne pas laisser l’extension sur un serveur sensible en production.

2.4 Utilisation avec les IDE

Détaillons l’intégration dans deux des principaux IDE pour PHP, que sont PhpStorm et VS Code.

Dans ces deux logiciels, on peut poser des points d’arrêt en cliquant dans la gouttière de marge. Un clic droit sur le breakpoint permet d’y adosser une condition d’arrêt (de type : $variable == ‘valeur’, afin que l’interception n’ait lieu sur cette ligne que lorsque la condition est vérifiée au runtime). On pourra se référer à la documentation de l’éditeur, pour ce qui est de la manipulation des contrôles et menus en eux-mêmes. Voyons comment brancher Xdebug.

2.4.1 PhpStorm

La prise en charge de Xdebug est native. Il suffira de sélectionner le port et d’activer l’interception dans les Préférences, sous Languages & Frameworks > PHP > Debug comme en figure 3. En particulier, on indiquera s’il faut ou non stopper l’exécution dès le démarrage du script.

hs-php-debug figure 03

Fig. 3 : Paramètres principaux pour Xdebug dans PhpStorm.

En outre, dans Languages & Frameworks > PHP > Servers (figure 4) il faut indiquer le mapping des fichiers : sous quel chemin du serveur se trouvent les fichiers dont on édite les sources localement. Ceci est nécessaire, car lors de la communication entre le serveur PHP et l’IDE, le module Xdebug voit les fichiers sous leur chemin côté serveur, et ne connaît pas l’arborescence sur la station de développement. En conséquence, tous les chemins concernés par la liste des breakpoints, la pile d’appels, etc. doivent subir une transposition de correspondance.

Notez que les mappings ne doivent pas obligatoirement suivre une arborescence directe : on peut notamment faire correspondre un répertoire distant en dehors de la racine du projet, avec un autre répertoire local. Ce cas peut se présenter par exemple si le projet utilise une bibliothèque partagée sur le serveur, dont on a aussi une copie locale, ou encore pour débuguer des scripts générés par un moteur de templates.

De plus, comme on peut être amené à travailler avec plusieurs serveurs (sachant que des containers Docker locaux sont assimilables à des serveurs distants), il y a la possibilité de définir des mappings différents pour chaque serveur.

v-hs-php-debug figure 04

Fig. 4 : Déclaration du mapping des chemins dans PhpStorm.

Une fois ces réglages effectués, on place PhpStorm en mode écoute, par le bouton indiqué en figure 5.

hs-php-debug figure 05

Fig. 5 : Toolbar de Debug dans PhpStorm.

2.4.2 VS Code

La prise en charge de Xdebug n’est pas préconstruite dans VS Code : il faut installer une extension. Nous utiliserons ici PHP Debug, de Felix Becker.

Une fois le plugin installé, on édite (ou crée) le fichier .vscode/launch.js à la racine du projet (on peut aussi utiliser l’assistant graphique comme en figure 6) pour y mettre le contenu suivant :

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "port": 9000,
      "pathMappings": {
        "/var/www/path-on-server/src": "${workspaceRoot}/src"
      }
    }
  ]
}

On retrouve ici essentiellement le port d’écoute et la déclaration de correspondance d’arborescences de fichiers entre le serveur et la station de travail (voir 2.4.1).

hs-php-debug figure 06

Fig. 6 : Menu de création d’un fichier de configuration de debugging dans VS Code.

Il faut ensuite relancer VS Code, puis, en se plaçant sur le mode Debugger dans la barre de gauche, on peut cliquer sur l’icône « Start » verte en face de la configuration Listen for Xdebug, pour placer l’IDE en écoute sur le port choisi.

3. Profiling et couverture de tests

Xdebug offre une possibilité de profiling du code : il s’agit de constituer un rapport, à l’issue de l’exécution d’une requête, pour y analyser le temps passé dans chaque fonction et la consommation mémoire à chaque étape.

Cet examen est extrêmement gourmand (les fichiers temporaires utilisés peuvent facilement atteindre plusieurs centaines de Mio) et ralentit perceptiblement l’exécution du programme. Aussi le réglage xdebug.profiler_enable est false par défaut.

La documentation [5] indique l’ensemble des réglages qui contrôlent le répertoire des fichiers temporaires de rapports, et comment activer le profiling sélectivement pour une requête donnée de façon similaire au 2.2.4.

Les rapports sont au format Cachegrind [7] et peuvent donc être visionnés avec les outils standard tels que KCacheGrind (exemple en figure 7).

v-hs-php-debug figure 07

Fig. 7 : Visualisation d’un rapport de profiling dans KCacheGrind

Xdebug sait aussi pister la couverture de code, utile pour déterminer les zones de code non traversées pendant les tests [9]. L’activation se contrôle par xdebug.coverage_enable et elle est même allumée par défaut. Cependant, les performances de Xdebug pour le code coverage ne sont pas les meilleures, et ce mode peut même ralentir considérablement les campagnes de test.

Il est préférable d’éteindre xdebug.coverage_enable = false, et d’utiliser Pcov [8], outil qui se spécialise dans cette voie, sans aptitude au debugging. Attention, les deux extensions sont mutuellement exclusives.

Conclusion

La mise en œuvre d’un environnement confortable et professionnel pour le debugging en PHP peut s’avérer décourageante au premier abord. Pourtant une fois correctement configuré, cet excellent outil permet aussi bien la détection de problèmes pendant le développement que le dépistage de situations incongrues sur un serveur en ligne.

Références

[1] Site du projet Xdebug : https://xdebug.org

[2] Protocole ouvert de débogage DBGp : https://xdebug.org/docs/dbgp

[3] G. ZERBIB, « Plongée dans l'OPcache », GNU/Linux Magazine no224, mars 2019 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-224/Plongee-dans-l-OPcache

[4] Extension vs Zend Extension : https://wiki.php.net/internals/extensions

[5] Paramètres de configuration Xdebug : https://xdebug.org/docs/all_settings

[6] Extension VS Code PHP Debug : https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug

[7] Cachegrind : https://valgrind.org/docs/manual/cg-manual.html

[8] PCov : https://github.com/krakjoe/pcov

[9] PHPUnit Code Coverage : https://phpunit.readthedocs.io/en/9.3/code-coverage-analysis.html

 



Article rédigé par

Par le(s) même(s) auteur(s)

Routage de services avec Traefik

Magazine
Marque
Linux Pratique
Numéro
126
Mois de parution
juillet 2021
Spécialité(s)
Résumé

Servir plusieurs applications web sur un même hôte ou derrière un même point d’entrée d’un réseau est une pratique aussi classique qu’indispensable dans de nombreuses situations. Sa mise en œuvre fait souvent appel à un laborieux reverse proxy Apache ou NginX. Mais voici Traefik Proxy, le couteau suisse du routage d’applications, léger et aux multiples atouts.

Automatiser les tests end-to-end en PHP

Magazine
Marque
GNU/Linux Magazine
Numéro
232
Mois de parution
décembre 2019
Spécialité(s)
Résumé

La partie frontale d'une application orientée utilisateur est généralement perçue comme difficile à tester de manière automatisée, et ces vérifications sont souvent reléguées à une campagne manuelle. Dans cet article, nous verrons comment utiliser l'outil Puppeteer dans un projet PHP, afin de garantir la validation déterministe de la partie d'une application web qui se joue dans le navigateur.

Les derniers articles Premiums

Les derniers articles Premium

Cryptographie : débuter par la pratique grâce à picoCTF

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

L’apprentissage de la cryptographie n’est pas toujours évident lorsqu’on souhaite le faire par la pratique. Lorsque l’on débute, il existe cependant des challenges accessibles qui permettent de découvrir ce monde passionnant sans avoir de connaissances mathématiques approfondies en la matière. C’est le cas de picoCTF, qui propose une série d’épreuves en cryptographie avec une difficulté progressive et à destination des débutants !

Game & Watch : utilisons judicieusement la mémoire

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Au terme de l'article précédent [1] concernant la transformation de la console Nintendo Game & Watch en plateforme de développement, nous nous sommes heurtés à un problème : les 128 Ko de flash intégrés au microcontrôleur STM32 sont une ressource précieuse, car en quantité réduite. Mais heureusement pour nous, le STM32H7B0 dispose d'une mémoire vive de taille conséquente (~ 1,2 Mo) et se trouve être connecté à une flash externe QSPI offrant autant d'espace. Pour pouvoir développer des codes plus étoffés, nous devons apprendre à utiliser ces deux ressources.

Raspberry Pi Pico : PIO, DMA et mémoire flash

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Le microcontrôleur RP2040 équipant la Pico est une petite merveille et malgré l'absence de connectivité wifi ou Bluetooth, l'étendue des fonctionnalités intégrées reste très impressionnante. Nous avons abordé le sujet du sous-système PIO dans un précédent article [1], mais celui-ci n'était qu'une découverte de la fonctionnalité. Il est temps à présent de pousser plus loin nos expérimentations en mêlant plusieurs ressources à notre disposition : PIO, DMA et accès à la flash QSPI.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 53 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous