> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP FFI

> Explica los fundamentos de PHP FFI, en qué entornos puede utilizarse y patrones prácticos de wrapper tomando como ejemplo VOICEVOX Core for PHP.

## 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.

<Warning>
  FFI no es una API PHP habitual bien abstraída. Un manejo incorrecto de punteros, propiedad o funciones de liberación puede provocar crashes o corrupción de memoria.
</Warning>

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.dll` en `php.ini`.
* Controlar la disponibilidad con `ffi.enable` en `php.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. |

<Info>
  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.
</Info>

Por tanto, aunque quieras usar FFI en una app Laravel, plantéate primero si puedes hacerlo desde una herramienta CLI o un proceso batch. Es más seguro evitar depender de que esté siempre disponible bajo FPM o Apache mod\_php.

## Uso básico

Los conceptos básicos de PHP FFI son cuatro: `FFI::cdef()`, `FFI::new()`, `FFI::addr()` y `FFI::string()`.

```php theme={null}
<?php

$ffi = FFI::cdef(<<<'CDEF'
typedef unsigned int time_t;
typedef unsigned int suseconds_t;

struct timeval {
    time_t tv_sec;
    suseconds_t tv_usec;
};

struct timezone {
    int tz_minuteswest;
    int tz_dsttime;
};

int gettimeofday(struct timeval *tv, struct timezone *tz);
CDEF, 'libc.so.6');

$tv = $ffi->new('struct timeval');
$tz = $ffi->new('struct timezone');

$ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz));

echo $tv->tv_sec.PHP_EOL;
```

Puntos clave de este ejemplo:

* `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 como `struct timeval *`.
* Puedes acceder a los campos de la estructura con `$tv->tv_sec`.

Para APIs que devuelven cadenas o binarios, usa `FFI::string()`.

```php theme={null}
<?php

$jsonPtr = $ffi->new('char*');

// Se asume que la parte C escribe el puntero resultado en $jsonPtr
$result = $ffi->some_function(FFI::addr($jsonPtr));

if ($result !== 0) {
    throw new RuntimeException('C API call failed.');
}

$json = FFI::string($jsonPtr);
```

<Tip>
  La memoria reservada con `FFI::new()` se libera normalmente con el conteo de referencias de PHP. Sin embargo, los punteros devueltos por una biblioteca C pueden requerir su propia función `free`. Comprueba, para cada API, de quién es la propiedad.
</Tip>

## Cargar ficheros de cabecera

Con `FFI::load()` puedes cargar declaraciones desde un fichero de cabecera C. En la cabecera puedes escribir `FFI_SCOPE` y `FFI_LIB`.

```c theme={null}
#define FFI_SCOPE "mylib"
#define FFI_LIB "/absolute/path/to/libmylib.so"

typedef struct MyContext MyContext;

MyContext *mylib_new(void);
void mylib_delete(MyContext *context);
```

```php theme={null}
<?php

FFI::load(__DIR__.'/mylib.h');

$ffi = FFI::scope('mylib');
$context = $ffi->mylib_new();
```

Ahora bien, la documentación oficial explica que ni `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:

<Steps>
  <Step title="Para APIs sencillas, escribir directamente en `FFI::cdef()`">
    Si hay pocos tipos y funciones dependientes, incluye solo las declaraciones necesarias en el propio PHP.
  </Step>

  <Step title="Para APIs complejas, prepara una cabecera preprocesada">
    Gestiona en un fichero aparte una cabecera pensada para FFI, con las macros y la compilación condicional ya eliminadas.
  </Step>
</Steps>

## Caso real: VOICEVOX Core for PHP

[VOICEVOX Core for PHP](/es/packages/voicevox-core-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: en `headers/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.

```c theme={null}
typedef struct VoicevoxSynthesizer VoicevoxSynthesizer;

typedef struct VoicevoxInitializeOptions {
    int32_t  acceleration_mode;
    uint16_t cpu_num_threads;
} VoicevoxInitializeOptions;

int32_t voicevox_synthesizer_new(
    const struct VoicevoxOnnxruntime *onnxruntime,
    const struct OpenJtalkRc *open_jtalk,
    struct VoicevoxInitializeOptions options,
    struct VoicevoxSynthesizer **out_synthesizer
);

void voicevox_synthesizer_delete(struct VoicevoxSynthesizer *synthesizer);
void voicevox_json_free(char *json);
void voicevox_wav_free(uint8_t *wav);
```

Lo importante de este diseño es que la estructura interna detallada de C no se expone al lado PHP: todo se encierra tras handles opacos y llamadas a funciones. El área que PHP tiene que manejar se reduce y las clases wrapper quedan más limpias.

### 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.

```php theme={null}
<?php

class VoicevoxFFI
{
    private static ?FFI $ffi = null;

    public static function getInstance(): FFI
    {
        return self::$ffi ??= FFI::cdef(
            file_get_contents(__DIR__.'/../headers/voicevox_core_ffi.h'),
            self::getLibraryPath(),
        );
    }

    public static function getLibraryPath(): string
    {
        if ($path = getenv('VOICEVOX_CORE_LIB_PATH')) {
            return $path;
        }

        return match (PHP_OS_FAMILY) {
            'Darwin' => 'libvoicevox_core.dylib',
            'Windows' => 'voicevox_core.dll',
            default => 'libvoicevox_core.so',
        };
    }
}
```

Con esta forma, tu aplicación no tiene que llamar directamente a `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 de `src/Synthesizer.php` reserva un `struct VoicevoxSynthesizer*` y pasa su dirección a `voicevox_synthesizer_new()`.

```php theme={null}
<?php

$options = $this->ffi->voicevox_make_default_initialize_options();
$options->acceleration_mode = $accelerationMode->value;
$options->cpu_num_threads = $cpuNumThreads;

$ptr = $this->ffi->new('struct VoicevoxSynthesizer*');

$result = $this->ffi->voicevox_synthesizer_new(
    $onnxruntime->handle(),
    $openJtalk->handle(),
    $options,
    FFI::addr($ptr),
);

$this->handle = $ptr;
```

Este patrón es la forma canónica de recibir un `Type **out_value` de la API C en PHP.

* Con `new('struct VoicevoxSynthesizer*')` creas la variable puntero.
* Con `FFI::addr($ptr)` pasas un `Type **`.
* 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 con `FFI::string()` y después llama a la función de liberación del lado C.

```php theme={null}
<?php

$jsonPtr = $this->ffi->voicevox_synthesizer_create_metas_json($this->handle);

$json = FFI::string($jsonPtr);
$this->ffi->voicevox_json_free($jsonPtr);

return $json;
```

```php theme={null}
<?php

$wavSize = $this->ffi->new('uint64_t');
$wavPtr = $this->ffi->new('uint8_t*');

$result = $this->ffi->voicevox_synthesizer_tts(
    $this->handle,
    $text,
    $styleId,
    $options,
    FFI::addr($wavSize),
    FFI::addr($wavPtr),
);

$wav = FFI::string($wavPtr, (int) $wavSize->cdata);
$this->ffi->voicevox_wav_free($wavPtr);

return $wav;
```

Este flujo de «copiar y liberar de inmediato» es uno de los patrones más importantes en FFI. Al no mezclar los buffers del lado C con el ciclo de vida de PHP, la gestión de la propiedad se simplifica.

## 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.                |

<Warning>
  Mezclar FFI directamente en el ciclo request/response de una app Laravel dificulta mucho tanto los cambios de entorno como el diagnóstico de fallos. Asegúrate primero de que funciona en comandos CLI o procesos puntuales fuera de las colas.
</Warning>

FFI es adecuado cuando ya existe una API C estable y no vale la pena crear una extensión propia de PHP. Al contrario, no es adecuado para funcionalidades que quieras servir desde hosting web normal ni para lógica que se pueda resolver únicamente con PHP.

## 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:

1. Preparar una cabecera aparte pensada para FFI.
2. Concentrar `FFI::cdef()` y la resolución de la ruta de la biblioteca en un único lugar.
3. Envolver los punteros opacos en clases dedicadas.
4. Copiar la memoria devuelta por C a PHP y liberarla mediante la función específica.

Si lees también la página de VOICEVOX Core for PHP, podrás ir y volver entre el concepto de FFI y el diseño real de un paquete para asentar el conocimiento.


## Related topics

- [VOICEVOX Core for PHP](/es/packages/voicevox-core-php/index.md)
- [Instalación y configuración - VOICEVOX for Laravel](/es/packages/laravel-voicevox/installation.md)
- [Modo nativo: Talk (síntesis de voz desde texto) - VOICEVOX for Laravel](/es/packages/laravel-voicevox/native-talk.md)
- [Modo nativo: Song (síntesis de voz cantada) - VOICEVOX for Laravel](/es/packages/laravel-voicevox/native-song.md)
- [VOICEVOX for Laravel](/es/packages/laravel-voicevox/index.md)
