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

> De basis van PHP FFI, de omgevingen waarin het beschikbaar is en praktische wrappatronen aan de hand van VOICEVOX Core for PHP.

## Inleiding

PHP FFI (Foreign Function Interface) is een PHP-extensie waarmee je shared libraries laadt, C-functies aanroept en toegang krijgt tot C-datastructuren. Zonder zelf een PHP-extensie te bouwen, kun je bestaande C-API's direct vanuit PHP gebruiken.

De officiële PHP-documentatie beschrijft FFI als een low-level en gevaarlijke functie. Alleen ontwikkelaars die C en de API van de doelbibliotheek begrijpen, zouden het moeten gebruiken.

<Warning>
  FFI is geen veilig geabstraheerde, gewone PHP-API. Fouten met pointers, ownership of vrijgavefuncties leiden tot crashes en geheugencorruptie.
</Warning>

FFI is ook geen "functie om dingen sneller te maken". Ook de officiële PHP-documentatie legt uit dat toegang tot datastructuren via FFI trager is dan native PHP-arrays en -objecten. De reden om FFI te kiezen is niet snelheid, maar dat je een bestaande C-bibliotheek vanuit PHP wilt gebruiken.

## Omgevingen en beperkingen van FFI

De officiële PHP-documentatie beschrijft het inschakelen van de FFI-extensie als volgt:

* PHP bouwen met `--with-ffi`
* Op Windows `php_ffi.dll` inschakelen in `php.ini`
* Met `ffi.enable` in `php.ini` bepalen of FFI gebruikt mag worden

`ffi.enable` kent drie waarden:

| Waarde    | Betekenis                                                            |
| --------- | -------------------------------------------------------------------- |
| `true`    | De FFI-API inschakelen                                               |
| `false`   | De FFI-API uitschakelen                                              |
| `preload` | De FFI-API alleen toestaan in de CLI SAPI en in gepreloade bestanden |

<Info>
  In webserveromgevingen wordt vaak `ffi.enable=false` of `ffi.enable=preload` gebruikt. Ook in hostingomgevingen, inclusief Laravel Cloud, is FFI niet altijd bruikbaar in een gewone webapp.
</Info>

Ook als je FFI in een Laravel-app wilt gebruiken, bekijk daarom eerst of het haalbaar is als CLI-tool of batchproces. Uitgaan van permanent ingeschakeld FFI onder FPM of Apache mod\_php kun je beter vermijden.

## Basisgebruik

De basis van PHP FFI bestaat uit vier onderdelen: `FFI::cdef()`, `FFI::new()`, `FFI::addr()` en `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;
```

De belangrijkste punten in dit voorbeeld:

* `FFI::cdef()` maakt een FFI-object van een string met C-declaraties en de naam van de shared library
* `$ffi->new('struct timeval')` alloceert een C-datastructuur
* `FFI::addr($tv)` maakt een pointerargument zoals `struct timeval *`
* Je benadert structvelden als `$tv->tv_sec`

Bij API's die strings of binaire data teruggeven, gebruik je `FFI::string()`.

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

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

// De C-kant schrijft naar verwachting een resultaatpointer in $jsonPtr
$result = $ffi->some_function(FFI::addr($jsonPtr));

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

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

<Tip>
  Geheugen dat je alloceert met `FFI::new()` wordt normaal vrijgegeven via de referentietelling van PHP. Pointers die de C-bibliotheek teruggeeft, moeten daarentegen soms worden vrijgegeven met een bibliotheekspecifieke `free`-functie. Controleer per API bij wie het ownership ligt.
</Tip>

## Headerbestanden inladen

Met `FFI::load()` laad je declaraties uit een C-headerbestand. In het headerbestand kun je `FFI_SCOPE` en `FFI_LIB` opnemen.

```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();
```

De officiële PHP-documentatie legt echter uit dat zowel `FFI::cdef()` als `FFI::load()` geen gewone C-preprocessorinstructies ondersteunt. `#include`, `#define` en conditionele compilatie kun je niet zomaar doorgeven.

In de praktijk kies je daarom meestal een van deze twee opties:

<Steps>
  <Step title="Bij een simpele API: direct in `FFI::cdef()` schrijven">
    Zijn er weinig afhankelijke types en functies, dan neem je alleen de benodigde declaraties op aan de PHP-kant.
  </Step>

  <Step title="Bij een complexe API: een voorbewerkte header maken">
    Beheer een aparte, FFI-specifieke header waaruit macro's en conditionele compilatie uit de oorspronkelijke header zijn verwijderd.
  </Step>
</Steps>

## Een echte use case: VOICEVOX Core for PHP

[VOICEVOX Core for PHP](/nl/packages/voicevox-core-php) is een praktijkvoorbeeld waarin de dynamische C-bibliotheek van VOICEVOX CORE vanuit pure PHP wordt gebruikt. Het bundelt de praktische FFI-patronen en is daardoor begrijpelijker dan abstracte uitleg.

### 1. Een voor FFI geschikt gemaakte header bijhouden

De oorspronkelijke header van VOICEVOX Core wordt niet direct gebruikt; in `headers/voicevox_core_ffi.h` zijn de declaraties voor FFI apart gezet. Daarin worden opaque pointers, structs en free-functies expliciet gemaakt.

```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);
```

Belangrijk in dit ontwerp is dat de gedetailleerde interne C-structuur niet aan de PHP-kant wordt getoond, maar wordt opgesloten in opaque handles en functieaanroepen. Het oppervlak dat je vanuit PHP moet beheren wordt kleiner en je wrapperklassen blijven overzichtelijk.

### 2. `FFI::cdef()` op één plek concentreren

`src/VoicevoxFFI.php` concentreert het inladen van de header en het bepalen van het bibliotheekpad op één plek.

```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',
        };
    }
}
```

In deze vorm hoeft je applicatie zelf `FFI::cdef()` niet aan te roepen. Ook verschillen in bibliotheekpaden vang je op met omgevingsvariabelen en OS-detectie.

### 3. Out parameters wrappen tot objecten

De constructor van `src/Synthesizer.php` alloceert een `struct VoicevoxSynthesizer*` en geeft het adres daarvan door aan `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;
```

Dit patroon is de basisvorm om een `Type **out_value` uit een C-API in PHP op te vangen:

* Maak een pointervariabele met `new('struct VoicevoxSynthesizer*')`
* Geef `Type **` door met `FFI::addr($ptr)`
* Bewaar de verkregen handle als property van een PHP-object

### 4. Door C teruggegeven geheugen naar een PHP-string kopiëren en direct vrijgeven

VOICEVOX Core for PHP kopieert JSON-strings en WAV-binaries na ontvangst eerst met `FFI::string()` naar een PHP-string en roept daarna de free-functie aan de C-kant aan.

```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;
```

Deze flow van "kopiëren en meteen vrijgeven" is een van de belangrijkste implementatiepatronen bij FFI. Door de buffer aan de C-kant niet te vermengen met de PHP-levenscyclus, houd je het ownership eenvoudig.

## Aandachtspunten en best practices

| Aspect          | Praktijkpunt                                                                                         |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| Uitvoeromgeving | Ontwerp in de eerste plaats voor de CLI                                                              |
| Headerbeheer    | Geef niet de oorspronkelijke header door, maar beheer aparte, voor FFI geschikt gemaakte declaraties |
| Bibliotheekpad  | Maak het omschakelbaar via absolute paden of omgevingsvariabelen                                     |
| Geheugenbeheer  | Kopieer met `FFI::string()` naar PHP en roep altijd de bibliotheekspecifieke vrijgavefunctie aan     |
| Wrapperontwerp  | Verspreid rauwe `CData` niet door de hele app, maar sluit die op in speciale klassen                 |
| Compatibiliteit | Controleer PHP-versies en ABI-wijzigingen van de doelbibliotheek samen                               |

<Warning>
  Meng je FFI direct in de request/response-cyclus van een Laravel-app, dan worden omgevingsverschillen en het isoleren van storingen ineens veel lastiger. Laat het eerst werken in een CLI-commando of een los eenmalig proces buiten de queue.
</Warning>

FFI is geschikt wanneer er al een stabiele C-API bestaat en het bouwen van een eigen PHP-extensie te ver gaat. Het is juist ongeschikt voor functionaliteit die op gewone webhosting moet draaien of voor verwerking die volledig in PHP kan.

## Samenvatting

Met PHP FFI kun je bestaande C-bibliotheken vanuit pure PHP aanroepen. Maar FFI is een low-level en gevaarlijk mechanisme, en het is niet altijd beschikbaar op een webserver.

Kijk je naar de implementatie van VOICEVOX Core for PHP, dan blijken in de praktijk deze vier punten belangrijk:

1. Houd een aparte header voor FFI bij
2. Concentreer `FFI::cdef()` en het bepalen van het bibliotheekpad op één plek
3. Wrap opaque pointers in speciale klassen
4. Kopieer door C teruggegeven geheugen naar PHP en geef het daarna vrij met de speciale functie

Lees ook de pagina over VOICEVOX Core for PHP; zo kun je heen en weer tussen de FFI-concepten en het echte packageontwerp.


## Related topics

- [VOICEVOX Core for PHP](/nl/packages/voicevox-core-php/index.md)
- [Installatie en configuratie - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/installation.md)
- [Native modus: talk (tekst-naar-spraak) - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/native-talk.md)
- [Native modus: song (zangsynthese) - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/native-song.md)
- [VOICEVOX for Laravel](/nl/packages/laravel-voicevox/index.md)
