⚙️ TypePHP : compiler du PHP en binaires natifs

📚 Introduction
PHP a passé les quinze dernières années à combler l’écart de performance avec les langages compilés : OPcache pour éviter de recompiler les opcodes à chaque requête, JIT depuis PHP 8.0, worker mode avec Swoole ou FrankenPHP pour garder l’application en mémoire entre les requêtes. Toutes ces approches optimisent l’exécution d’un programme qui reste, au fond, interprété par le Zend Engine.
TypePHP prend un chemin différent. C’est un compilateur Ahead-Of-Time open source, publié par l’équipe derrière Swoole, qui traduit du code PHP en C++ puis en code machine natif. Pas de bytecode, pas de VM à l’exécution : le programme compilé tourne directement sur le CPU, comme un binaire C++ classique.
Le projet est jeune et se présente lui-même comme expérimental. Il ne vise pas la compatibilité totale avec n’importe quel script PHP, mais un sous-ensemble du langage suffisamment défini pour être compilé de façon fiable. C’est cette frontière, plus que la performance brute, qui détermine si TypePHP a sa place dans un projet.
🔎 Comment TypePHP compile-t-il du PHP en binaire ?
Le pipeline tient en quatre étapes : le compilateur parse le PHP source (accompagné, si besoin, de fichiers .stub.php qui déclarent des signatures C++ ou de bibliothèques externes), construit le modèle complet des symboles du projet, abaisse chaque corps de fonction en C++17, puis invoque un compilateur natif pour produire un exécutable, une extension PHP ou une bibliothèque partagée.
Ce découpage en deux phases (préparation puis conversion) permet de connaître tous les symboles du projet avant de générer quoi que ce soit, ce qui rend les builds multi-fichiers et le bootstrap auto-hébergé déterministes. Le compilateur tpc en est la meilleure preuve : il est écrit entièrement en PHP, et se compile lui-même avec TypePHP.
Trois modes de sortie sont disponibles, sélectionnés avec -m :
| Mode | Flag | Sortie | Cas d’usage |
|---|---|---|---|
| Binaire | -m bin (défaut) | Exécutable | CLI, services autonomes |
| Extension | -m ext | .so / .dll PHP | Charger des fonctions compilées dans un SAPI PHP |
| Bibliothèque | -m lib | Lib partagée + .stub.php | Réutiliser une API TypePHP depuis un autre projet |
Un premier essai tient en quelques lignes :
<?php
function main(): void
{
echo "Hello World!\n";
var_dump(PHP_VERSION);
}bin/tpc.php hello.php
./helloLe mode binaire impose une fonction main() globale (sans paramètre, ou main(int $argc, array $argv) pour récupérer les arguments), et interdit toute instruction exécutable au niveau du scope global : le code doit vivre dans une fonction ou une méthode.
🚀 Installation et prérequis
TypePHP s’installe comme dépendance de dev :
composer require --dev swoole/typephp
vendor/bin/tpc.php project.ymlMais contrairement à la plupart des packages Composer, il embarque une vraie chaîne de compilation native. Sur Ubuntu ou Debian :
sudo apt install build-essential cmake pkg-config libgmp-dev libmpfr-devIl faut en plus PHP 8.4 ou 8.5 avec les headers de développement et php-config, GCC 9+ (ou Clang) supportant le C++17, CMake 3.24+, et la bibliothèque PHP embed (libphp.so sur Linux) pour les builds en mode binaire ou bibliothèque partagée. Si libphp.so est absent, tpc.php propose de le télécharger et de le construire à la demande.
Linux x64 reste la plateforme de développement et de CI principale. Windows, macOS (x64 et ARM64) et WASI 0.2 sont également ciblés, mais leur disponibilité dépend de la présence des toolchains et bibliothèques tierces sur la machine hôte.
🧩 Typage natif et attributs de génération de code
Le gain de performance de TypePHP vient surtout du typage explicite. Avec use native_types, les scalaires (int, float, bool) sont stockés en types C++ natifs (int64_t, double, bool) au lieu de la structure zval de Zend, ce qui transforme l’arithmétique en instructions CPU directes :
<?php
use native_types;
function fib(int $n): int
{
if ($n == 1 || $n == 2) {
return 1;
}
return fib($n - 1) + fib($n - 2);
}
function main(int $argc, array $argv): void
{
$n = (int)$argv[1];
echo fib($n) . "\n";
}Le même mécanisme couvre les conteneurs (std::vector, std::map, std::ordered_map) et les nombres à haute précision (bigInt sur GMP, decimal sur libmpdec, bigFloat sur MPFR), avec des méthodes typées :
<?php
use native_types;
function main(): void
{
$c = std::decimal("0.1")->add(std::decimal("0.2"));
echo $c->toString() . "\n"; // "0.3", exact
}TypePHP ajoute aussi des attributs de génération de code, résolus à la compilation et sans coût à l’exécution : #[Getter], #[Setter], #[With], #[Constructor], #[Printer] et #[Arrayable] produisent des méthodes typées directement à partir des déclarations de propriétés.
<?php
#[Printer(fields: ['id', 'name'])]
final class User
{
#[Constructor, Getter, With]
public int $id;
#[Constructor, Getter, Setter]
public string $name = 'guest';
}
function main(): void
{
$user = new User(7);
$user->setName('Alice');
$copy = $user->withId(8);
echo $user->getId(); // 7
echo $copy->getId(); // 8
echo $user; // User(id=7, name=Alice)
}📊 Quels gains de performance offre la compilation AOT ?
Le dépôt embarque les benchmarks officiels du langage PHP (bench.php et micro_bench.php, tirés de php-src) compilés en -O3 :
| Benchmark | PHP interprété | TypePHP AOT (-O3) | Gain |
|---|---|---|---|
bench.php | 5,034 s | 0,603 s | ~8x |
micro_bench.php | 13,045 s | 2,021 s | ~6,5x |
Sur une boucle de mise à jour de 10 000 x 100 000 éléments, un std::array TypePHP tourne à 6,4 s contre 67,6 s pour un tableau PHP classique (JIT actif), soit environ 10x, et s’approche du std::vector C++ pur (6,2 s). Le projet précise lui-même que ces chiffres sont un instantané, pas une garantie : la version de PHP, le compilateur, le CPU et les optimisations activées font varier le résultat, à comparer sur sa propre machine et sa propre charge avant toute décision de déploiement.
🎯 Cas d’usage concrets
Certains profils de projet tirent nettement plus parti de la compilation AOT que d’autres, vu le sous-ensemble de langage supporté.
Un outil CLI interne distribué comme binaire. Un script d’admin, un générateur de rapports, un utilitaire de migration : compilé en -m bin, il devient un exécutable unique à copier sur la machine cible, sans installer PHP ni ses extensions.
Un job de traitement de données. Parsing de gros fichiers, agrégations, boucles numériques. Avec use native_types et les conteneurs std::vector ou std::array, on retrouve l’ordre de grandeur des benchmarks du dépôt : 6 à 10 fois plus rapide qu’une boucle PHP équivalente.
Un calcul à précision garantie. Les types bigInt (GMP), decimal (libmpdec) et bigFloat (MPFR) évitent l’imprécision de l’arithmétique flottante binaire — utile pour un moteur de taux, une conversion de devises, ou tout calcul où 0.1 + 0.2 doit renvoyer exactement 0.3.
Un point chaud isolé dans une application existante. Le mode -m ext compile juste la fonction critique (parsing, hashing, un algorithme métier) en extension .so, chargée dans le SAPI PHP habituel, sans réécrire le reste de l’application.
Une bibliothèque réutilisable. Le mode -m lib génère une bibliothèque partagée accompagnée de son .stub.php, consommable depuis un autre projet comme un binding natif.
Une cible WASI ou navigateur. Les flags --wasm et --wasm=browser (via jco) compilent vers WebAssembly 0.2, pour embarquer une fonction PHP dans un runtime edge ou côté client.
Une application web complète avec ses contrôleurs, son ORM et ses packages tiers reste, pour l’instant, hors de ce périmètre : c’est l’objet de la section suivante.
⚠️ Quelques précautions
TypePHP n’est pas un remplacement transparent de PHP-FPM ou de FrankenPHP. Trois points à garder en tête avant d’y toucher sur un vrai projet.
La compatibilité est volontairement restreinte. Le scope global déclaratif uniquement, la signature stricte de main() en mode binaire, et le typage figé de use native_types (une variable ne peut plus changer vers un type incompatible) supposent d’écrire le code en pensant au compilateur, pas de recompiler tel quel une application existante.
Le déploiement se complexifie. Un binaire produit en mode bin embarque ou lie PHPX, libphp et les bibliothèques natives configurées : le paquet de déploiement doit toutes les inclure. C’est l’inverse de la promesse d’un simple composer install en production.
Le projet est jeune. TypePHP se décrit comme en développement actif, avec un modèle de compatibilité qui évolue (positive et négative testée, documentée dans la liste des fonctionnalités incompatibles plutôt que garantie par défaut). Le compilateur est distribué sous licence GPL-3.0 : à vérifier selon le contexte d’usage, même si cette licence concerne l’outil de compilation, pas automatiquement le code qu’il produit.
🎉 Conclusion
TypePHP n’est pas une alternative à FrankenPHP : les deux projets ne résolvent pas le même problème. FrankenPHP garde le PHP interprété mais élimine le coût du bootstrap répété entre les requêtes. TypePHP va plus loin en supprimant l’interprétation elle-même, pour un sous-ensemble du langage compilé en code machine.
Le résultat le plus intéressant n’est pas l’exécutable web, c’est le CLI : un outil interne, un job de traitement de données, une bibliothèque de calcul intensif écrits en PHP mais distribués comme un binaire unique, sans runtime à installer sur la machine cible. Sur ce terrain, la promesse rejoint celle des CLI Go ou Rust, avec la syntaxe PHP en plus.
Pour une application web complète en production, la prudence reste de mise : le sous-ensemble de compatibilité, la chaîne de compilation native à maintenir et la jeunesse du projet en font aujourd’hui un candidat à surveiller plutôt qu’à adopter tel quel.
🔗 Liens utiles
/faq
Questions fréquentes
Qu'est-ce que TypePHP ?
+
TypePHP est un compilateur Ahead-Of-Time open source créé par l'équipe Swoole. Il traduit du code PHP en C++, puis en code machine natif, au lieu de l'interpréter à l'exécution. Le compilateur est lui-même écrit en PHP et se compile avec son propre outil (self-hosting).
TypePHP est-il compatible avec une application PHP existante ?
+
Pas directement. TypePHP supporte volontairement un sous-ensemble défini et testé du langage : le scope global est déclaratif uniquement, le mode binaire impose une fonction main() stricte, et certains patterns dynamiques (réflexion avancée, closures, références) restent non supportés. Il faut lire la liste des fonctionnalités incompatibles avant d'envisager une migration.
Quels gains de performance offre TypePHP ?
+
Sur les benchmarks officiels du langage PHP (bench.php et micro_bench.php), TypePHP compilé en -O3 est environ 8 fois et 6,5 fois plus rapide que le PHP interprété. Sur une boucle intensive de mise à jour de tableau, son type std::array est environ 10 fois plus rapide qu'un tableau PHP classique.
Comment installer TypePHP ?
+
Via Composer : composer require --dev swoole/typephp, puis vendor/bin/tpc.php project.yml pour compiler. Il faut aussi les outils natifs du système : GCC 9+ ou Clang avec C++17, CMake 3.24+, et les bibliothèques GMP et MPFR pour les types haute précision.