Introducción
PHP FFI (Foreign Function Interface) es una extensión de PHP que permite cargar bibliotecas compartidas, llamar a funciones C y acceder a estructuras de datos C. No hace falta crear una extensión de PHP propia: puedes usar directamente las API de C existentes desde PHP. La documentación oficial de PHP describe FFI como una funcionalidad de bajo nivel y peligrosa. Solo debería usarla quien entienda a fondo C y la API de la biblioteca objetivo. FFI tampoco es una función «para acelerar». La documentación oficial de PHP indica que el acceso a estructuras de datos vía FFI es más lento que usar arrays u objetos nativos de PHP. El motivo para elegir FFI no es la velocidad, sino querer usar una biblioteca C existente desde PHP.Entornos donde se puede usar FFI y limitaciones
La documentación oficial de PHP explica cómo habilitar la extensión FFI:- Compilar PHP con
--with-ffi. - En Windows, habilitar
php_ffi.dllenphp.ini. - Controlar la disponibilidad con
ffi.enableenphp.ini.
ffi.enable puede tomar tres valores.
| Valor | Significado |
|---|---|
true | Habilita la API FFI. |
false | Deshabilita la API FFI. |
preload | Permite la API FFI solo en el CLI SAPI y en archivos ya precargados. |
En entornos de servidor web es habitual
ffi.enable=false o ffi.enable=preload. Incluso en hosting como Laravel Cloud, no se puede dar por hecho que se pueda usar FFI en una aplicación web normal.Uso básico
Los conceptos básicos de PHP FFI son cuatro:FFI::cdef(), FFI::new(), FFI::addr() y FFI::string().
FFI::cdef()crea el objeto FFI a partir de una cadena de declaraciones C y el nombre de la biblioteca compartida.$ffi->new('struct timeval')reserva una estructura C.FFI::addr($tv)produce un argumento puntero comostruct timeval *.- Puedes acceder a los campos de la estructura con
$tv->tv_sec.
FFI::string().
Cargar ficheros de cabecera
ConFFI::load() puedes cargar declaraciones desde un fichero de cabecera C. En la cabecera puedes escribir FFI_SCOPE y FFI_LIB.
FFI::cdef() ni FFI::load() admiten las directivas normales del preprocesador C. No puedes pasar #include, #define ni compilación condicional tal cual.
Por eso, en producción se suele optar por una de estas dos vías:
Para APIs sencillas, escribir directamente en `FFI::cdef()`
Si hay pocos tipos y funciones dependientes, incluye solo las declaraciones necesarias en el propio PHP.
Caso real: VOICEVOX Core for PHP
VOICEVOX Core for PHP es un ejemplo real de cómo usar la biblioteca dinámica C de VOICEVOX CORE desde PHP puro. Reúne patrones prácticos de FFI, así que resulta más fácil de entender que las explicaciones abstractas.1. Tener una cabecera adaptada a FFI
VOICEVOX Core no utiliza la cabecera original tal cual: enheaders/voicevox_core_ffi.h se extraen las declaraciones necesarias para FFI. Se declaran de forma explícita los punteros opacos, las estructuras y las funciones de liberación.
2. Concentrar FFI::cdef() en un solo sitio
src/VoicevoxFFI.php concentra en un único lugar la carga de la cabecera y la resolución de la ruta de la biblioteca.
FFI::cdef(). Las diferencias en la ruta de la biblioteca las absorbe una variable de entorno y una comprobación del SO.
3. Envolver los parámetros de salida en objetos
El constructor desrc/Synthesizer.php reserva un struct VoicevoxSynthesizer* y pasa su dirección a voicevox_synthesizer_new().
Type **out_value de la API C en PHP.
- Con
new('struct VoicevoxSynthesizer*')creas la variable puntero. - Con
FFI::addr($ptr)pasas unType **. - El handle obtenido se guarda como propiedad de un objeto PHP.
4. Copia a cadena PHP la memoria devuelta por C y libérala enseguida
En cuanto VOICEVOX Core for PHP recibe una cadena JSON o binarios WAV, primero los copia a una cadena PHP conFFI::string() y después llama a la función de liberación del lado C.
Precauciones y buenas prácticas
| Aspecto | Recomendación |
|---|---|
| Entorno de ejecución | Diseña asumiendo primero un uso CLI. |
| Gestión de la cabecera | No pases la cabecera original; mantén por separado una versión adaptada a FFI. |
| Ruta de la biblioteca | Permite alternar mediante rutas absolutas o variables de entorno. |
| Gestión de memoria | Copia a PHP con FFI::string() y llama siempre a la función de liberación propia de la biblioteca. |
| Diseño del wrapper | No expandas el CData bruto por toda la app; enciérralo en clases dedicadas. |
| Compatibilidad | Comprueba a la vez la versión de PHP y los cambios de ABI de la biblioteca objetivo. |
Resumen
Con PHP FFI puedes invocar bibliotecas C existentes desde PHP puro. Pero FFI es un mecanismo de bajo nivel y peligroso, y no siempre está disponible en un servidor web. Al mirar la implementación de VOICEVOX Core for PHP, se ve que en la práctica hay cuatro puntos clave:- Preparar una cabecera aparte pensada para FFI.
- Concentrar
FFI::cdef()y la resolución de la ruta de la biblioteca en un único lugar. - Envolver los punteros opacos en clases dedicadas.
- Copiar la memoria devuelta por C a PHP y liberarla mediante la función específica.