Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Comment PHP s’exĂ©cute

PHP n’exĂ©cute pas votre application : il exĂ©cute votre script, une fois, pour une requĂȘte, puis il s’arrĂȘte. Il n’y a pas d’objet serveur Ă  instancier, pas d’appel Ă  listen(), pas de boucle d’évĂ©nements. Quelque chose d’extĂ©rieur Ă  PHP (un serveur web, ou vous dans un terminal) lance l’interprĂ©teur, l’interprĂ©teur exĂ©cute un fichier de haut en bas, et tout ce qu’il a allouĂ© est libĂ©rĂ© quand le fichier se termine.

Tous les autres chapitres de ce livre sont plus faciles une fois celui-ci assimilé.

Deux portes d’entrĂ©e

Depuis un terminal, PHP se comporte comme Python ou Ruby :

php hello.php
php -r 'echo PHP_VERSION, PHP_EOL;'
php -a          # interactive shell
php -l file.php # syntax check only
php -S localhost:8000   # development web server, current folder as document root

Sur le web, PHP n’est pas le serveur. Le serveur web reçoit la requĂȘte HTTP et la transmet Ă  PHP, le plus souvent via PHP-FPM, un pool de processus PHP en attente derriĂšre nginx, Apache ou Caddy. Un processus prend la requĂȘte, exĂ©cute le script auquel l’URL correspond, Ă©crit la rĂ©ponse, nettoie, et retourne attendre la suivante. Le pool compte autant de processus que vous en configurez, et c’est ainsi que PHP utilise tous vos cƓurs, avec des processus plutĂŽt qu’avec des threads.

Une boucle en quatre Ă©tapes : un navigateur envoie une requĂȘte, un Ă©lĂ©phant tout neuf se rĂ©veille dans une piĂšce vide, il construit la rĂ©ponse sur un Ă©tabli, puis la remet et la piĂšce est nettoyĂ©e pour la requĂȘte suivante

La couche qui relie l’interprĂ©teur au monde extĂ©rieur s’appelle une SAPI (server API). La ligne de commande, FPM et le mod_php d’Apache sont trois SAPI diffĂ©rentes posĂ©es sur le mĂȘme moteur ; seule la plomberie change de l’une Ă  l’autre.

Rien de partagé

C’est la partie qui change votre façon d’écrire du code. Rien ne survit d’une requĂȘte Ă  la suivante Ă  l’intĂ©rieur de PHP. Une propriĂ©tĂ© statique que vous affectez, une globale, une connexion ouverte, un objet mis en cache dans un tableau : tout cela existe le temps d’une requĂȘte, puis disparaĂźt.

<?php
declare(strict_types=1);

final class Counter
{
    public static int $hits = 0;
}

Counter::$hits++;
echo Counter::$hits; // 1, on every single request, forever

ExĂ©cutez ce code sous un serveur web, rechargez la page cent fois, et il affiche 1 cent fois, lĂ  oĂč le mĂȘme code en Node ou en Java compterait jusqu’à 100. Aucun des deux comportements n’est un bug, ce sont simplement deux modĂšles diffĂ©rents.

Les consĂ©quences s’enchaĂźnent :

  • Un bug n’affecte qu’une requĂȘte. Fuite mĂ©moire, boucle infinie, exception non rattrapĂ©e : le processus qui traitait cette requĂȘte meurt ou est recyclĂ©, et la requĂȘte suivante en reçoit un neuf.
  • La montĂ©e en charge est horizontale par construction. Plus de trafic demande plus de processus FPM ou plus de machines, et rien dans l’application n’a besoin d’ĂȘtre thread-safe, puisque rien n’est partagĂ©.
  • L’état vit hors de PHP. Les sessions vont dans des fichiers, une base de donnĂ©es ou un stockage clĂ©-valeur. Les caches vont dans OPcache et APCu (mĂ©moire partagĂ©e entre les processus d’une machine) ou dans un stockage externe comme Redis ou Memcached. La connexion Ă  la base s’ouvre au dĂ©but de la requĂȘte et se ferme Ă  la fin ; le pooling, si vous en avez besoin, se fait dans un pooler devant la base, pas dans PHP.
  • Le coĂ»t de dĂ©marrage se paie Ă  chaque requĂȘte. C’est ce qui explique que PHP dĂ©marre vite, et que l’écosystĂšme accorde autant d’attention Ă  l’autoloading, Ă  OPcache et au prĂ©chargement.

Si vous vous surprenez Ă  concevoir un singleton pour « garder la connexion ouverte entre les requĂȘtes », arrĂȘtez-vous : dans ce modĂšle, il n’existe aucun moment entre deux requĂȘtes.

Le cache de bytecode

Lire et compiler chaque fichier Ă  chaque requĂȘte serait lent, alors PHP ne le fait pas. OPcache conserve la forme compilĂ©e de chaque fichier en mĂ©moire partagĂ©e, et la requĂȘte suivante la rĂ©utilise. Il est livrĂ© avec PHP et activĂ© par dĂ©faut dans toute installation sĂ©rieuse.

En dĂ©veloppement, OPcache vĂ©rifie les dates des fichiers et recompile ce qui a changĂ©, donc la boucle Ă©diter-recharger fonctionne telle quelle, sans Ă©tape de build ni watcher. En production, la vĂ©rification des dates est en gĂ©nĂ©ral dĂ©sactivĂ©e pour gagner du temps, ce qui signifie qu’un dĂ©ploiement doit rĂ©initialiser le cache, en redĂ©marrant FPM ou en appelant opcache_reset(). Quand on l’oublie, on obtient le classique « j’ai dĂ©ployĂ© et rien n’a changĂ© ».

OPcache hĂ©berge aussi le compilateur JIT (PHP 8.0). Il aide surtout les scripts gourmands en CPU, et ce n’est pas lui qui rend une application web rapide, donc voyez-le comme une option Ă  essayer plutĂŽt que comme une fondation.

PHP persistant

Le modĂšle sans Ă©tat partagĂ© est le comportement par dĂ©faut, pas une loi. Plusieurs runtimes gardent votre application en mĂ©moire entre les requĂȘtes, comme le ferait un serveur Node ou Java : FrankenPHP en mode worker, RoadRunner, et Swoole ou OpenSwoole. Votre amorçage s’exĂ©cute une fois, puis une boucle vous tend les requĂȘtes l’une aprĂšs l’autre.

À gauche : une rangĂ©e de petites piĂšces identiques, chacune avec un Ă©lĂ©phant neuf, une requĂȘte qui entre et une rĂ©ponse qui sort, puis la piĂšce vidĂ©e. À droite : une grande piĂšce avec un seul Ă©lĂ©phant qui reste Ă  son bureau pendant qu'une file de requĂȘtes dĂ©file

Le gain est rĂ©el, puisque le coĂ»t d’amorçage disparaĂźt et que les connexions peuvent vraiment rester ouvertes. Le coĂ»t est celui que vous connaissez dĂ©jĂ  des autres langages : l’état fuit si vous ne le nettoyez pas, une fuite mĂ©moire grossit, et un compteur statique compte pour de bon. Les frameworks qui prennent en charge ces runtimes rĂ©initialisent leur conteneur entre deux requĂȘtes prĂ©cisĂ©ment pour cela. Commencez avec FPM, et passez Ă  un runtime worker quand vous aurez mesurĂ© une raison de le faire.

Ce qu’il y a dans la boüte

L’interprĂ©teur est un cƓur en C plus des extensions, certaines intĂ©grĂ©es et toujours actives, d’autres compilĂ©es au build, d’autres installĂ©es Ă  part. php -m liste ce que contient votre build. Celles dont vous remarquerez l’absence sur une installation fraĂźche sont en gĂ©nĂ©ral pdo_mysql ou pdo_pgsql (pilotes de base de donnĂ©es), intl (collation Unicode, formatage), mbstring (chaĂźnes multi-octets), curl, gd ou imagick (images), et xdebug (dĂ©bogueur, dĂ©veloppement seulement).

Votre gestionnaire de paquets les fournit sous forme de paquets sĂ©parĂ©s (php-intl, php-mbstring, etc.), et l’image Docker officielle fournit docker-php-ext-install. Les extensions non livrĂ©es avec PHP viennent de PECL, ou de PIE, l’installeur d’extensions plus rĂ©cent, dans l’esprit de Composer.

La configuration vit dans php.ini. La ligne de commande et FPM lisent des fichiers ini diffĂ©rents, ce qui explique qu’un script se comporte d’une façon dans un terminal et d’une autre sous le serveur web. php --ini montre les fichiers que charge la CLI, et phpinfo() dans une page montre ceux que charge FPM. Deux rĂ©glages comptent dĂšs le premier jour : memory_limit (128 Mo par dĂ©faut sous FPM, illimitĂ© en CLI) et max_execution_time (30 secondes sous FPM, illimitĂ© en CLI).

Le piĂšge

En venant d’un runtime persistant, la premiĂšre erreur est d’attendre que la mĂ©moire persiste, avec un cache dans un tableau statique, une classe « pool de connexions » ou un compteur pour limiter le dĂ©bit. Sous FPM, tout cela ne sert silencieusement Ă  rien. La seconde erreur est le miroir de la premiĂšre : aprĂšs le passage Ă  un runtime worker, une valeur propre Ă  une requĂȘte rangĂ©e dans une statique se retrouve partagĂ©e entre les utilisateurs.

Posez une seule question sur tout Ă©tat : doit-il survivre Ă  cette requĂȘte ? Si oui, sa place n’est pas dans la mĂ©moire de PHP, mais dans la base, dans un cache ou dans la session.

Une fois ce modùle en place, la syntaxe est la partie facile, et c’est l’objet du chapitre La syntaxe.