Skip to main content

Introduzione

PHP FFI (Foreign Function Interface) è un’estensione di PHP che permette di caricare librerie condivise, chiamare funzioni C e accedere a strutture dati C. Senza scrivere estensioni PHP native puoi usare direttamente da PHP API C già esistenti. La documentazione ufficiale di PHP descrive FFI come una funzionalità di basso livello e pericolosa. Va usata solo da chi conosce a fondo C e l’API della libreria di destinazione.
FFI non è la solita API PHP astratta in modo sicuro. Errori con puntatori, ownership o funzioni di liberazione possono causare crash o corruzione della memoria.
FFI non è nemmeno “una funzione per velocizzare”. Anche la documentazione ufficiale spiega che l’accesso alle strutture dati via FFI è più lento rispetto ad array e oggetti PHP nativi. Il motivo per scegliere FFI non è la velocità: è poter usare da PHP librerie C già esistenti.

Ambienti in cui FFI è utilizzabile e limitazioni

La documentazione ufficiale di PHP indica i seguenti modi per abilitare l’estensione FFI.
  • Compilare PHP con --with-ffi
  • Su Windows abilitare php_ffi.dll in php.ini
  • Controllare l’abilitazione con ffi.enable in php.ini
ffi.enable accetta i seguenti tre valori.
Sui web server è comune trovare ffi.enable=false o ffi.enable=preload. Anche sugli hosting (incluso Laravel Cloud) non è detto che tu possa usare FFI in una normale web app.
Perciò, anche in un’app Laravel, per prima cosa considera se il caso d’uso può essere risolto con tool CLI o batch. Meglio evitare di dare per scontato che FFI sia sempre attivo su FPM o Apache mod_php.

Uso di base

Le basi di PHP FFI sono quattro: FFI::cdef(), FFI::new(), FFI::addr() e FFI::string().
Punti chiave dell’esempio:
  • FFI::cdef() crea un oggetto FFI da una stringa di dichiarazioni C e dal nome di una libreria condivisa
  • $ffi->new('struct timeval') alloca una struttura dati C
  • FFI::addr($tv) produce un argomento puntatore come struct timeval *
  • Puoi accedere ai campi della struttura con $tv->tv_sec
Per API che restituiscono stringhe o binari usa FFI::string().
La memoria allocata con FFI::new() viene rilasciata dal reference counting di PHP. I puntatori restituiti da una libreria C, invece, richiedono spesso di essere liberati con la specifica funzione free della libreria. Verifica caso per caso a chi appartiene la memoria.

Caricare file header

Con FFI::load() puoi caricare le dichiarazioni da un file header C. Nell’header puoi scrivere FFI_SCOPE e FFI_LIB.
Tuttavia, la documentazione ufficiale precisa che né FFI::cdef()FFI::load() supportano le normali direttive del preprocessore C. #include, #define, la compilazione condizionale non possono essere passate così come sono. Per questo, nella pratica, si sceglie una delle due strade seguenti.
1

Per API semplici scrivi direttamente in `FFI::cdef()`

Se i tipi e le funzioni da cui dipendi sono pochi, incorpori le sole dichiarazioni necessarie lato PHP.
2

Per API complesse prepara un header pre-processato

Gestisci in un file separato un header dedicato all’FFI, ripulito da macro e compilazione condizionale.

Caso d’uso reale: VOICEVOX Core for PHP

VOICEVOX Core for PHP è un esempio concreto che usa la libreria dinamica C di VOICEVOX CORE da PHP puro. Raccoglie pattern pratici di FFI, quindi è un caso di studio più concreto delle spiegazioni astratte.

1. Un header preparato per FFI

L’header originale di VOICEVOX Core non viene usato così com’è: si estraggono le dichiarazioni utili in headers/voicevox_core_ffi.h, che espone puntatori opachi, struct e funzioni di free.
Il punto importante è non esporre lato PHP i dettagli interni del C: si mantengono solo handle opachi e chiamate a funzione. La superficie da gestire in PHP è minore e le classi wrapper sono più ordinate.

2. Concentrare FFI::cdef() in un unico punto

src/VoicevoxFFI.php concentra in un solo punto il caricamento dell’header e la risoluzione del path della libreria.
Con questa forma non chiami direttamente FFI::cdef() dall’applicazione. Le differenze di path della libreria vengono assorbite da variabile d’ambiente e detection dell’OS.

3. Wrappare gli out parameter in oggetti

Il costruttore in src/Synthesizer.php alloca struct VoicevoxSynthesizer* e passa l’indirizzo a voicevox_synthesizer_new().
È il pattern base per ricevere in PHP i Type **out_value delle API C.
  • Crea una variabile puntatore con new('struct VoicevoxSynthesizer*')
  • Passa Type ** con FFI::addr($ptr)
  • Conserva l’handle ottenuto come proprietà dell’oggetto PHP

4. Copia la memoria restituita dal C in una stringa PHP e libera subito

VOICEVOX Core for PHP, quando riceve stringhe JSON o binari WAV, li copia prima in una stringa PHP con FFI::string() e poi chiama subito la funzione di free lato C.
Questo flusso “copia e libera subito” è uno dei pattern più importanti in FFI. Non mescolando i buffer C con il ciclo di vita di PHP, l’ownership resta semplice.

Note e best practice

Innestare direttamente FFI nel ciclo request/response di un’app Laravel rende improvvisamente più difficili le differenze d’ambiente e il triage dei problemi. Prima fai funzionare tutto in comandi CLI o processi puntuali diversi dalla queue.
FFI è adatto quando c’è già un’API C stabile ma non vale la pena scrivere un’estensione PHP. Al contrario non è adatto a funzionalità che vuoi far girare su hosting web standard, o a lavorazioni che si esauriscono in PHP. Con PHP FFI puoi chiamare da PHP puro librerie C già esistenti. Ma FFI è un meccanismo di basso livello e pericoloso, e non è detto che sia utilizzabile su un web server. Guardando l’implementazione di VOICEVOX Core for PHP si nota che nella pratica sono importanti quattro punti.
  1. Preparare un header dedicato all’FFI
  2. Concentrare in un solo punto FFI::cdef() e la risoluzione del path della libreria
  3. Incapsulare i puntatori opachi in classi dedicate
  4. Copiare in PHP la memoria restituita dal C e liberarla con la funzione specifica
Se leggi anche la pagina di VOICEVOX Core for PHP, puoi alternare tra concetti FFI e design reale di un pacchetto per una comprensione più profonda.
Ultima modifica il 13 luglio 2026