Découverte de FrankenPHP

Disponible dans 1 jour

Devenir premium
Résumé Support

Aujourd'hui, lorsque l'on veut héberger une application PHP, on a recours à 2 briques : un serveur web comme Nginx ou Apache, puis PHP-FPM pour interpréter les fichiers PHP. Si on utilise Docker, cela implique deux conteneurs qui doivent communiquer ensemble. FrankenPHP propose un serveur web tout-en-un capable de servir les fichiers statiques et d'exécuter le code PHP.

Sommaire

Pourquoi FrankenPHP ?

L'intérêt principal de FrankenPHP est de réduire la complexité de configuration mais il intègre aussi plusieurs fonctionnalités intéressantes :

  • un mode worker pour éviter de redémarrer toute l'application à chaque requête.
  • le support des Early Hints pour indiquer plus tôt au navigateur les ressources à charger.
  • Mercure, pour gérer du temps réel avec des Server-Sent Events.
  • un système de rechargement à chaud pour le serveur de développement.

FrankenPHP est basé sur Caddy, ce qui permet de profiter de son système de configuration et de sa gestion automatique des certificats SSL lorsqu'on utilise un nom de domaine.

Installation et première utilisation

FrankenPHP peut être installé comme binaire sur le système ou utilisé dans un conteneur.
Pour démarrer rapidement un serveur web depuis le dossier courant, on peut utiliser :

frankenphp php-server

Cette commande sert automatiquement les fichiers du dossier courant. Si un fichier index.php se trouve à la racine, il sera interprété lorsque l'on visite localhost dans le navigateur. On peut aussi préciser l'adresse d'écoute et le dossier racine de l'application. Dans le cas d'une application Laravel, la racine publique est le dossier public :

frankenphp php-server --listen :8000 --root public

Contrairement au serveur interne de PHP, FrankenPHP est multi-thread par défaut, ce qui permet de traiter plusieurs requêtes en même temps.

On peut aussi exécuter directement des scripts PHP en ligne de commande :

frankenphp php-cli index.php

Configurer via Caddyfile

Pour éviter la répétition, on peut créer un fichier Caddyfile à la racine du projet. FrankenPHP étant basé sur Caddy, il reprend son système de configuration. Une configuration minimale pour le développement ressemblera à ça :

:8000 { root * public php_server { try_files {path} index.php } }

On indique ici :

  • le port sur lequel écouter, ici :8000.
  • le dossier racine de l'application avec root * public.
  • la directive php_server, spécifique à FrankenPHP.
  • un try_files pour servir les fichiers statiques lorsqu'ils existent, puis rediriger vers index.php sinon.

Une fois le fichier en place, il suffit de lancer :

frankenphp run

FrankenPHP trouve automatiquement le Caddyfile et démarre le serveur avec cette configuration. Pendant le développement, on peut demander à FrankenPHP de surveiller les changements de configuration avec le drapeau watch :

frankenphp run --watch

Hot reload

On peut aussi activer le rechargement à chaud qui va rafraîchir les pages lors des modifications du code source. Dans ce cas-là, frankenphp utilisera Mercure et son système de Server-Sent Events pour indiquer les changements.

:8000 { root * public php_server { hot_reload resources/views/**/*{.blade.php} try_files {path} index.php } mercure { anonymous } }

Le mode worker

Le mode worker est l'une des fonctionnalités les plus importantes de FrankenPHP. Avec PHP-FPM, chaque requête exécute l'application PHP, génère une réponse, puis repart de zéro pour la requête suivante. Sur une grosse application, il faut donc recharger le framework et une grande quantité de code à chaque nouvelle requête.

Le mode worker change cette logique : l'application est démarrée une fois puis conservée en mémoire pour gérer le traitement de plusieurs requêtes.

La configuration dépend du framework utilisé. Dans Laravel, on peut passer par Laravel Octane :

composer require laravel/octane php artisan octane:install

Lors de l'installation, on choisit FrankenPHP. Octane ajoute alors un fichier frankenphp-worker.php dans le dossier public. Ce fichier démarre l'application, puis exécute une logique spécifique pour le traitement de chaque nouvelle requête, ce qui permet de conserver une partie de l'application entre les requêtes.

Pour tester rapidement, on peut démarrer Octane via PHP artisan :

php artisan octane:start

Ou en modifiant la configuration :

{ frankenphp { worker public/frankenphp-worker.php } } :8000 { root * public php_server { try_files {path} frankenphp-worker.php } }

Un mode de fonctionnement différent

Le mode worker implique une différence importante : le code PHP n'est pas entièrement réexécuté entre chaque requête. Si on modifie une propriété statique sur une classe, elle va être conservée pendant la durée de vie du worker. Lorsque l'on active le mode worker, il faut donc être conscient des implications.

Il faudra faire particulièrement attention au pattern Singleton, car il peut se retrouver partagé entre plusieurs requêtes. Dans le cas de Laravel, les services enregistrés sous forme de singleton ne sont pas conservés entre les requêtes, mais si on veut conserver un service spécifique pendant la durée de vie du worker, il faut le déclarer dans la configuration warm d'Octane.

Performance et déploiement

Le gain de performance dépend de la lourdeur de l'application mais peut être significatif pour des frameworks qui ont tendance à charger beaucoup de logique à l'initialisation. Le mieux est de faire un test dans votre situation pour mesurer l'impact que cela peut avoir.

Enfin, quand on déploie une nouvelle version de l'application et que l'on veut redémarrer les workers à distance, on peut appeler l'API d'administration de Caddy avec une commande curl pour demander le redémarrage des workers.

curl -X POST http://localhost:2019/frankenphp/workers/restart

Early Hints

FrankenPHP supporte les Early Hints, qui permettent de renvoyer des en-têtes spécifiques pour indiquer au navigateur les fichiers qui seront utilisés par la page.

L'idée est d'indiquer le plus tôt possible au navigateur les ressources qu'il devra charger, par exemple les fichiers CSS ou JavaScript. Le navigateur peut alors commencer à les récupérer avant même de recevoir le moindre HTML.

Mercure

FrankenPHP intègre aussi Mercure qui permet de faire du temps réel avec des Server-Sent Events. Le serveur peut publier des événements, qui sont ensuite reçus par les navigateurs ou d'autres périphériques.

mercure { # The secret key used to sign the JWT tokens for publishers publisher_jwt !ChangeThisMercureHubJWTSecretKey! # When publisher_jwt is set, you must set subscriber_jwt too! subscriber_jwt !ChangeThisMercureHubJWTSecretKey! # Allows anonymous subscribers (without JWT) anonymous }
  • une souscription anonyme, pratique pour envoyer des notifications globales.
  • une clé secrète subscriber_jwt pour générer des tokens JWT pour pouvoir s'abonner.
  • une clé secrète publisher_jwt, qui sera en général utilisée par le serveur pour publier des événements.

Créer un exécutable autonome

FrankenPHP permet aussi de créer un exécutable autonome. L'idée est de compiler l'application pour obtenir un binaire qui contient à la fois le code de l'application et le serveur FrankenPHP. Cet exécutable permettra de lancer l'application de manière autonome.

Bilan

FrankenPHP apporte surtout une configuration plus simple. Pour un projet PHP, on peut installer FrankenPHP, écrire une configuration avec le nom de domaine, et laisser Caddy gérer les certificats. Cela évite une configuration plus lourde avec Nginx et PHP-FPM.

Pour aller plus loin, vous pouvez consulter la documentation officielle de FrankenPHP.