Zum Hauptinhalt springen

Einleitung

PHP FFI (Foreign Function Interface) ist eine PHP-Erweiterung, mit der Sie Shared Libraries laden, C-Funktionen aufrufen und auf C-Datenstrukturen zugreifen. Sie können vorhandene C-APIs direkt aus PHP nutzen, ohne selbst eine PHP-Extension zu schreiben. Die offizielle PHP-Dokumentation beschreibt FFI als eine Low-Level- und potenziell gefährliche Funktion. Sie sollte nur von Entwicklern eingesetzt werden, die C und die API der Zielbibliothek verstehen.
FFI ist keine sicher abstrahierte, gewöhnliche PHP-API. Falsche Handhabung von Pointern, Ownership oder Freigabefunktionen kann zu Abstürzen oder Speicherfehlern führen.
FFI ist auch keine „Performance-Funktion”. Selbst die offizielle Doku weist darauf hin, dass der Datenstruktur-Zugriff via FFI langsamer ist als native PHP-Arrays oder -Objekte. Der Grund für FFI ist nicht Geschwindigkeit, sondern die Nutzung bestehender C-Bibliotheken aus PHP heraus.

Verfügbare Umgebungen und Einschränkungen

Die offizielle PHP-Dokumentation nennt folgende Wege, um die FFI-Erweiterung zu aktivieren:
  • PHP mit --with-ffi bauen
  • Unter Windows in php.ini php_ffi.dll aktivieren
  • Über ffi.enable in php.ini die Verfügbarkeit steuern
ffi.enable kennt drei Werte:
WertBedeutung
trueDie FFI-API ist aktiviert
falseDie FFI-API ist deaktiviert
preloadFFI ist nur in der CLI-SAPI und für preloadete Dateien erlaubt
In Webserver-Umgebungen wird häufig ffi.enable=false oder ffi.enable=preload gesetzt. Auch bei Hosting-Plattformen einschließlich Laravel Cloud lässt sich FFI in einer klassischen Webanwendung nicht garantiert nutzen.
Wenn Sie FFI in einer Laravel-App einsetzen wollen, prüfen Sie deshalb zunächst, ob die Aufgabe in einem CLI-Tool oder Batch-Prozess erledigt werden kann. Sich darauf zu verlassen, dass FFI unter FPM oder Apache mod_php dauerhaft aktiv ist, ist riskant.

Grundlagen der Nutzung

Die vier zentralen Bausteine von PHP FFI sind FFI::cdef(), FFI::new(), FFI::addr() und FFI::string().
<?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;
Merken Sie sich zu diesem Beispiel:
  • FFI::cdef() erzeugt aus einer C-Deklaration und einem Bibliotheksnamen ein FFI-Objekt.
  • $ffi->new('struct timeval') allokiert die C-Datenstruktur.
  • FFI::addr($tv) erzeugt ein Pointer-Argument wie struct timeval *.
  • Auf Struktur-Felder greifen Sie über $tv->tv_sec zu.
Für APIs, die Strings oder Binärdaten liefern, nutzen Sie FFI::string().
<?php

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

// Annahme: Die C-Seite schreibt den Ergebnis-Pointer in $jsonPtr
$result = $ffi->some_function(FFI::addr($jsonPtr));

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

$json = FFI::string($jsonPtr);
Von FFI::new() allokierter Speicher wird üblicherweise über PHP-Referenzzähler freigegeben. Pointer, die eine C-Bibliothek zurückgibt, müssen aber unter Umständen mit der bibliothekseigenen free-Funktion freigegeben werden. Prüfen Sie pro API, welche Ownership zutrifft.

Header-Dateien einlesen

FFI::load() liest Deklarationen aus einer C-Header-Datei. Diese kann FFI_SCOPE und FFI_LIB enthalten.
#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

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

$ffi = FFI::scope('mylib');
$context = $ffi->mylib_new();
Allerdings weist die offizielle PHP-Dokumentation darauf hin, dass weder FFI::cdef() noch FFI::load() gewöhnliche C-Präprozessor-Direktiven verarbeiten. #include, #define und Bedingungskompilate lassen sich nicht direkt übergeben. In der Praxis wählt man daher meist einen dieser zwei Wege:
1

Bei einfachen APIs direkt in `FFI::cdef()` schreiben

Sind wenige Typen und Funktionen betroffen, betten Sie die benötigten Deklarationen direkt im PHP-Code ein.
2

Bei komplexen APIs eine vorverarbeitete Header-Datei pflegen

Verwalten Sie einen speziell für FFI aufbereiteten Header — ohne Makros und Bedingungen — als separate Datei.

Praxisbeispiel: VOICEVOX Core for PHP

VOICEVOX Core for PHP ist ein reales Beispiel, das die dynamische C-Bibliothek von VOICEVOX CORE aus reinem PHP ansteuert. Die dortigen FFI-Muster sind kompakt zusammengefasst und daher leichter nachzuvollziehen als abstrakte Erklärungen.

1. Ein FFI-eigenes Header-File pflegen

Der Original-Header von VOICEVOX Core wird nicht direkt verwendet. Stattdessen werden in headers/voicevox_core_ffi.h FFI-spezifische Deklarationen ausgelagert, unter anderem für opake Pointer, Structs und free-Funktionen.
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);
Entscheidend an diesem Design ist, dass die detaillierten internen Strukturen der C-Seite nicht an PHP durchgereicht werden — es zeigen sich nur opake Handles und Funktionsaufrufe. So bleibt die zu handhabende Fläche in PHP klein, und Wrapper-Klassen fallen sauberer aus.

2. FFI::cdef() an einer Stelle konzentrieren

src/VoicevoxFFI.php bündelt das Header-Laden und die Bibliothekspfad-Auflösung an einem Ort.
<?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',
        };
    }
}
Damit muss der Rest Ihrer App FFI::cdef() nicht direkt aufrufen. Auch Unterschiede beim Bibliothekspfad lassen sich über eine Umgebungsvariable und Betriebssystem-Erkennung abfangen.

3. Out-Parameter kapseln und in Objekte überführen

Der Konstruktor in src/Synthesizer.php allokiert einen struct VoicevoxSynthesizer* und übergibt dessen Adresse an voicevox_synthesizer_new().
<?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;
Dieses Muster ist die Grundform, wie Sie den C-Ausdruck Type **out_value in PHP annehmen:
  • new('struct VoicevoxSynthesizer*') legt die Pointer-Variable an.
  • FFI::addr($ptr) liefert das Type **-Argument.
  • Das erhaltene Handle halten Sie als Property des PHP-Objekts.

4. Von C zurückgegebenen Speicher in einen PHP-String kopieren und sofort freigeben

VOICEVOX Core for PHP kopiert JSON-Strings und WAV-Binärdaten zunächst mit FFI::string() in einen PHP-String und ruft anschließend die C-seitige Free-Funktion auf.
<?php

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

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

return $json;
<?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;
Dieses Muster „kopieren und sofort freigeben” ist eines der wichtigsten Implementierungsmuster für FFI. Sie halten den C-Buffer aus dem PHP-Lifecycle heraus und vereinfachen die Ownership.

Hinweise und Best Practices

AspektPraxis-Tipp
LaufzeitumgebungEntwerfen Sie zunächst für die CLI
Header-VerwaltungReichen Sie den Original-Header nicht durch — pflegen Sie FFI-Deklarationen separat
BibliothekspfadMachen Sie ihn per absolutem Pfad oder Umgebungsvariable umschaltbar
SpeicherverwaltungKopieren Sie über FFI::string() und rufen Sie zwingend die zugehörige Free-Funktion auf
Wrapper-DesignHalten Sie CData nicht app-weit sichtbar, kapseln Sie es in eigene Klassen
KompatibilitätPrüfen Sie PHP-Version und ABI-Änderungen der Zielbibliothek gemeinsam
Wenn Sie FFI direkt in den Request/Response-Zyklus Ihrer Laravel-App einbetten, werden Umgebungsunterschiede und Fehleranalysen schnell schwierig. Machen Sie es zunächst in CLI-Kommandos oder als Einzelprozess außerhalb der Queue produktionsreif.
FFI eignet sich für Fälle, in denen eine bereits stabile C-API existiert und der Aufwand, eine PHP-Extension zu schreiben, zu groß wäre. Für Features, die im normalen Web-Hosting laufen sollen, oder für rein in PHP realisierbare Verarbeitung ist FFI nicht der richtige Weg.

Fazit

Mit PHP FFI rufen Sie vorhandene C-Bibliotheken aus reinem PHP heraus auf. FFI ist jedoch ein Low-Level- und riskanter Mechanismus, der sich nicht immer in Webservern nutzen lässt. Die Implementierung von VOICEVOX Core for PHP zeigt, was in der Praxis zählt:
  1. Einen eigenen FFI-Header pflegen.
  2. FFI::cdef() und die Bibliothekspfad-Auflösung an einer Stelle bündeln.
  3. Opake Pointer in eigenen Klassen kapseln.
  4. Von C zurückgegebenen Speicher in PHP kopieren und mit der dedizierten Free-Funktion freigeben.
Wenn Sie zusätzlich die Seite zu VOICEVOX Core for PHP lesen, verbinden Sie das Konzept von FFI direkt mit einem konkreten Paketdesign.
Zuletzt geändert am 13. Juli 2026