メインコンテンツへスキップ

はじめに

PHP FFI(Foreign Function Interface)は、共有ライブラリを読み込み、C 関数を呼び出し、C のデータ構造へアクセスするための PHP 拡張です。PHP 拡張を自作しなくても、既存の C API を PHP から直接使えます。 PHP 公式ドキュメントは、FFI を低レベルで危険な機能として説明しています。C と対象ライブラリの API を理解した開発者だけが使うべき機能です。
FFI は安全に抽象化された通常の PHP API ではありません。ポインタ、所有権、解放関数を誤るとクラッシュやメモリ破壊につながります。
FFI は「速くするための機能」でもありません。PHP 公式ドキュメントでも、FFI のデータ構造アクセスはネイティブな PHP 配列やオブジェクトより遅いと説明されています。FFI を選ぶ理由は速度ではなく、既存の C ライブラリを PHP から使いたいことです。

FFI が使える環境と制限

PHP 公式ドキュメントでは、FFI 拡張を有効にする方法を次のように説明しています。
  • PHP を --with-ffi 付きでビルドする
  • Windows では php.iniphp_ffi.dll を有効にする
  • php.iniffi.enable で使用可否を制御する
ffi.enable は次の 3 つの値を取ります。
Web サーバー環境では ffi.enable=falseffi.enable=preload が使われることが多いです。Laravel Cloud を含むホスティング環境でも、通常の Web アプリとして FFI を使えるとは限りません。
そのため、Laravel アプリで FFI を使う場合でも、まずは CLI ツールやバッチ処理で成立するかを考えてください。FPM や Apache mod_php で常時有効にする前提は避けたほうが安全です。

基本的な使い方

PHP FFI の基本は FFI::cdef()FFI::new()FFI::addr()FFI::string() の 4 つです。
この例で押さえる点は次のとおりです。
  • FFI::cdef() は C の宣言文字列と共有ライブラリ名から FFI オブジェクトを作る
  • $ffi->new('struct timeval') は C のデータ構造を確保する
  • FFI::addr($tv)struct timeval * のようなポインタ引数を作る
  • $tv->tv_sec のように構造体フィールドへアクセスできる
文字列やバイナリを受け取る API では FFI::string() を使います。
FFI::new() で確保したメモリは、通常は PHP の参照カウントで解放されます。一方で、C ライブラリが返したポインタはライブラリ専用の free 関数で解放する場合があります。どちらの所有権なのかを API ごとに確認してください。

ヘッダーファイルの読み込み

FFI::load() を使うと、C ヘッダーファイルから宣言を読み込めます。ヘッダーファイルには FFI_SCOPEFFI_LIB を書けます。
ただし、PHP 公式ドキュメントは FFI::cdef()FFI::load() のどちらでも通常の C プリプロセッサ命令は使えないと説明しています。#include#define、条件付きコンパイルをそのまま渡すことはできません。 そのため、実運用では次のどちらかを選ぶことが多いです。
1

単純な API なら `FFI::cdef()` に直接書く

依存する型や関数が少ないなら、必要な宣言だけを PHP 側へ埋め込みます。
2

複雑な API なら前処理済みヘッダーを用意する

元のヘッダーからマクロや条件付きコンパイルを取り除いた、FFI 用のヘッダーを別ファイルで管理します。

実際のユースケース: VOICEVOX Core for PHP

VOICEVOX Core for PHP は、VOICEVOX CORE の C 動的ライブラリを Pure PHP から使う実例です。FFI の実用パターンがまとまっているので、抽象的な説明より理解しやすい題材です。

1. FFI 用に整形したヘッダーを持つ

VOICEVOX Core の元ヘッダーはそのままでは使わず、headers/voicevox_core_ffi.h に FFI 向けの宣言を切り出しています。ここでは不透明ポインタ、構造体、free 関数を明示しています。
この設計で重要なのは、C の詳細な内部構造を PHP 側へ見せずに、不透明ハンドルと関数呼び出しに閉じ込めていることです。PHP から扱う面積が小さくなり、ラッパークラスも整理しやすくなります。

2. FFI::cdef() を 1 箇所へ集約する

src/VoicevoxFFI.php は、ヘッダー読み込みとライブラリパス解決を 1 箇所へ集約しています。
この形にすると、あなたのアプリ側は FFI::cdef() を直接呼ばずに済みます。ライブラリパスの差分も、環境変数と OS 判定で吸収できます。

3. out parameter をラップしてオブジェクト化する

src/Synthesizer.php のコンストラクタは、struct VoicevoxSynthesizer* を確保し、voicevox_synthesizer_new() にそのアドレスを渡しています。
このパターンは、C API の Type **out_value を PHP で受ける基本形です。
  • new('struct VoicevoxSynthesizer*') でポインタ変数を作る
  • FFI::addr($ptr)Type ** を渡す
  • 取得したハンドルを PHP オブジェクトのプロパティに保持する

4. C が返したメモリは PHP 文字列へコピーしてすぐ解放する

VOICEVOX Core for PHP は、JSON 文字列や WAV バイナリを受け取ったら、まず FFI::string() で PHP 文字列へコピーし、その後で C 側の free 関数を呼んでいます。
この「コピーして即解放」の流れは、FFI で最も重要な実装パターンの 1 つです。C 側のバッファを PHP のライフサイクルへ混ぜないことで、所有権を単純化できます。

注意点とベストプラクティス

Laravel アプリの request/response サイクルに FFI を直接混ぜると、環境差分と障害切り分けが急に難しくなります。まずは CLI コマンドやキュー以外の単発プロセスで成立させてください。
FFI が向いているのは、すでに安定した C API があり、PHP 拡張を自作するほどではないケースです。逆に、通常の Web ホスティングで動かしたい機能や、PHP だけで完結できる処理には向いていません。

まとめ

PHP FFI を使うと、あなたは既存の C ライブラリを Pure PHP から呼び出せます。ただし、FFI は低レベルで危険な仕組みです。Web サーバーで常に使えるとも限りません。 VOICEVOX Core for PHP の実装を見ると、実践では次の 4 点が重要だと分かります。
  1. FFI 用のヘッダーを別に用意する
  2. FFI::cdef() とライブラリパス解決を 1 箇所へ集約する
  3. 不透明ポインタを専用クラスでラップする
  4. C が返したメモリは PHP にコピーしてから専用関数で解放する
VOICEVOX Core for PHP のページも合わせて読むと、FFI の概念と実際のパッケージ設計を往復しながら理解できます。
最終更新日 2026年5月16日