Introduction
PHP FFI (Foreign Function Interface) est une extension PHP permettant de charger des bibliothèques partagées, d’appeler des fonctions C et d’accéder à des structures de données C. Sans écrire votre propre extension PHP, vous pouvez utiliser directement des API C existantes depuis PHP. La documentation officielle PHP décrit FFI comme une fonctionnalité bas niveau et dangereuse. Seuls les développeurs qui comprennent le C et l’API de la bibliothèque cible devraient l’utiliser. FFI n’est pas non plus « une fonctionnalité pour accélérer les choses ». La documentation officielle PHP explique que l’accès aux structures de données via FFI est plus lent que via les tableaux et objets PHP natifs. La raison de choisir FFI n’est pas la vitesse, mais l’envie d’utiliser une bibliothèque C existante depuis PHP.Environnements et contraintes d’utilisation
La documentation officielle PHP décrit ainsi l’activation de l’extension FFI :- Compiler PHP avec
--with-ffi - Sous Windows, activer
php_ffi.dlldansphp.ini - Contrôler la disponibilité via
ffi.enabledansphp.ini
ffi.enable peut prendre l’une des trois valeurs suivantes.
| Valeur | Signification |
|---|---|
true | Active l’API FFI |
false | Désactive l’API FFI |
preload | Autorise l’API FFI uniquement pour la SAPI CLI et les fichiers préchargés |
En environnement de serveur web,
ffi.enable=false ou ffi.enable=preload est fréquent. Même sur des hébergeurs comme Laravel Cloud, l’utilisation de FFI dans une application web ordinaire n’est pas forcément possible.Utilisation de base
Les bases de PHP FFI reposent sur quatre éléments :FFI::cdef(), FFI::new(), FFI::addr(), FFI::string().
FFI::cdef()crée un objet FFI depuis une chaîne de déclarations C et un nom de bibliothèque partagée$ffi->new('struct timeval')alloue la structure de données CFFI::addr($tv)crée un argument pointeur du typestruct timeval *- On peut accéder aux champs de la structure comme
$tv->tv_sec
FFI::string().
Charger des fichiers d’en-tête
FFI::load() permet de charger des déclarations depuis un fichier d’en-tête C. Le fichier d’en-tête peut contenir FFI_SCOPE et FFI_LIB.
FFI::cdef() ni FFI::load() n’acceptent les directives de préprocesseur C ordinaires. Vous ne pouvez pas passer tels quels #include, #define, ni de la compilation conditionnelle.
En pratique, on choisit donc l’une de ces deux approches :
Pour une API simple, écrivez directement dans `FFI::cdef()`
Si les types et fonctions dépendants sont peu nombreux, intégrez uniquement les déclarations nécessaires dans le PHP.
Cas concret : VOICEVOX Core for PHP
VOICEVOX Core for PHP est un exemple concret d’utilisation en Pure PHP de la bibliothèque dynamique C VOICEVOX CORE. C’est un sujet plus facile à comprendre qu’une description abstraite car il rassemble des patterns d’utilisation pratique de FFI.1. Avoir un en-tête préparé pour FFI
L’en-tête original de VOICEVOX Core n’est pas utilisé tel quel : on isole les déclarations dédiées à FFI dansheaders/voicevox_core_ffi.h. On y explicite les pointeurs opaques, les structures et les fonctions de libération.
2. Centraliser FFI::cdef() en un seul endroit
src/VoicevoxFFI.php centralise le chargement de l’en-tête et la résolution du chemin de la bibliothèque.
FFI::cdef(). Les différences de chemin de bibliothèque sont absorbées par les variables d’environnement et la détection d’OS.
3. Envelopper les out parameters pour en faire des objets
Le constructeur desrc/Synthesizer.php alloue un struct VoicevoxSynthesizer* et passe son adresse à voicevox_synthesizer_new().
Type **out_value d’une API C.
- Créer une variable pointeur avec
new('struct VoicevoxSynthesizer*') - Passer
Type **avecFFI::addr($ptr) - Conserver le handle obtenu dans une propriété d’un objet PHP
4. Copier la mémoire renvoyée par C dans une chaîne PHP puis libérer immédiatement
VOICEVOX Core for PHP, dès qu’il reçoit une chaîne JSON ou un binaire WAV, la copie d’abord dans une chaîne PHP viaFFI::string(), puis appelle la fonction free côté C.
Points d’attention et bonnes pratiques
| Aspect | Recommandation |
|---|---|
| Environnement d’exécution | Concevoir d’abord pour CLI |
| Gestion des en-têtes | Ne pas passer l’en-tête d’origine ; gérer séparément une version dédiée à FFI |
| Chemin de bibliothèque | Rendre commutable via chemin absolu ou variable d’environnement |
| Gestion mémoire | Copier en PHP via FFI::string() puis appeler impérativement la fonction de libération dédiée |
| Conception du wrapper | Ne pas diffuser les CData bruts dans l’application ; les enfermer dans des classes dédiées |
| Compatibilité | Vérifier ensemble la version PHP et les changements d’ABI de la bibliothèque cible |
Récapitulatif
Avec PHP FFI, vous pouvez appeler des bibliothèques C existantes depuis Pure PHP. Toutefois, FFI est un mécanisme bas niveau et dangereux, qui n’est pas nécessairement toujours disponible sur un serveur web. En regardant l’implémentation de VOICEVOX Core for PHP, on constate qu’en pratique quatre points sont essentiels :- Préparer un en-tête dédié à FFI
- Centraliser
FFI::cdef()et la résolution de chemin de bibliothèque en un seul endroit - Envelopper les pointeurs opaques dans des classes dédiées
- Copier la mémoire renvoyée par C en PHP puis la libérer avec la fonction dédiée