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

> Découvrez les bases de PHP FFI, les environnements disponibles et un pattern de wrapping concret basé sur VOICEVOX Core for PHP.

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

<Warning>
  FFI n'est pas une API PHP ordinaire abstraite de manière sûre. Une erreur sur les pointeurs, la propriété (ownership) ou les fonctions de libération peut entraîner des crashes ou une corruption mémoire.
</Warning>

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.dll` dans `php.ini`
* Contrôler la disponibilité via `ffi.enable` dans `php.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 |

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

Aussi, même pour utiliser FFI dans une application Laravel, envisagez d'abord si vous pouvez vous en tirer avec un outil CLI ou un traitement batch. Il est plus prudent d'éviter de compter sur un FFI activé en permanence sous FPM ou Apache mod\_php.

## Utilisation de base

Les bases de PHP FFI reposent sur quatre éléments : `FFI::cdef()`, `FFI::new()`, `FFI::addr()`, `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;
```

Points à retenir dans cet exemple :

* `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 C
* `FFI::addr($tv)` crée un argument pointeur du type `struct timeval *`
* On peut accéder aux champs de la structure comme `$tv->tv_sec`

Pour les API qui reçoivent des chaînes ou des binaires, utilisez `FFI::string()`.

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

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

// Supposons que le C écrive un pointeur résultat dans $jsonPtr
$result = $ffi->some_function(FFI::addr($jsonPtr));

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

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

<Tip>
  La mémoire allouée par `FFI::new()` est habituellement libérée par le comptage de références PHP. À l'inverse, un pointeur retourné par une bibliothèque C se libère parfois via une fonction `free` propre à la bibliothèque. Vérifiez la propriété de chaque API.
</Tip>

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

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

Cependant, la documentation officielle PHP explique que ni `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 :

<Steps>
  <Step title="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.
  </Step>

  <Step title="Pour une API complexe, préparez un en-tête pré-traité">
    Gérez dans un fichier séparé un en-tête pour FFI, débarrassé des macros et de la compilation conditionnelle de l'en-tête d'origine.
  </Step>
</Steps>

## Cas concret : VOICEVOX Core for PHP

[VOICEVOX Core for PHP](/fr/packages/voicevox-core-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 dans `headers/voicevox_core_ffi.h`. On y explicite les pointeurs opaques, les structures et les fonctions de libération.

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

L'important dans cette conception, c'est que les détails internes du C ne sont pas exposés au PHP : on les enferme derrière un handle opaque et des appels de fonctions. La surface exposée à PHP est réduite et les classes wrappers plus faciles à organiser.

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

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

Ainsi, l'application côté vôtre n'a plus besoin d'appeler directement `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 de `src/Synthesizer.php` alloue un `struct VoicevoxSynthesizer*` et passe son adresse à `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;
```

Ce pattern est la forme de base pour recevoir en PHP un `Type **out_value` d'une API C.

* Créer une variable pointeur avec `new('struct VoicevoxSynthesizer*')`
* Passer `Type **` avec `FFI::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 via `FFI::string()`, puis appelle la fonction `free` côté 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;
```

Ce flux « copier puis libérer immédiatement » est l'un des patterns d'implémentation les plus importants avec FFI. En ne mêlant pas les buffers du C au cycle de vie PHP, on simplifie la propriété.

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

<Warning>
  Mélanger directement FFI au cycle requête/réponse d'une application Laravel rend les différences d'environnement et le triage des incidents rapidement difficiles. Faites d'abord fonctionner le tout dans une commande CLI ou un processus ponctuel hors queue.
</Warning>

FFI est indiqué lorsque vous avez déjà une API C stable et que créer une extension PHP serait excessif. À l'inverse, il n'est pas indiqué pour des fonctionnalités qui doivent tourner sur un hébergement web ordinaire, ni pour du traitement qui peut se faire en PHP seul.

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

1. Préparer un en-tête dédié à FFI
2. Centraliser `FFI::cdef()` et la résolution de chemin de bibliothèque en un seul endroit
3. Envelopper les pointeurs opaques dans des classes dédiées
4. Copier la mémoire renvoyée par C en PHP puis la libérer avec la fonction dédiée

Lire aussi la page de VOICEVOX Core for PHP permet de faire des allers-retours entre le concept FFI et la conception concrète d'un package.


## Related topics

- [VOICEVOX Core for PHP](/fr/packages/voicevox-core-php/index.md)
- [Installation et configuration - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/installation.md)
- [Mode natif : Talk (synthèse vocale de texte) - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/native-talk.md)
- [Mode natif : Song (synthèse vocale chantée) - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/native-song.md)
- [VOICEVOX for Laravel](/fr/packages/laravel-voicevox/index.md)
