PHP FFI
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). PHPStan can detect FFI usage but cannot automatically identify performance anti-patterns like frequent small calls, memory leaks from unfreed C allocations, or uncached FFI::cdef() in web requests. Detection hints explicitly mark automated detection as 'no'. Most issues only surface through profiling or runtime observation.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes FFI as an alternative to writing a PHP extension, but fixing FFI misuse (batching operations, implementing memory management, caching FFI instances) requires restructuring how the C library is called throughout a component. Not a one-line fix, but typically contained to the FFI integration layer.
Closest to 'persistent productivity tax' (b5). FFI applies to cli and queue-worker contexts, not web requests. Once introduced, FFI creates ongoing maintenance concerns: memory management discipline, C header version tracking, and ensuring ext-ffi availability across environments. Every developer touching FFI code must understand C memory semantics, creating a knowledge tax beyond typical PHP work.
Closest to 'serious trap' (t7). The misconception explicitly states developers assume 'FFI is faster than PHP' when actually FFI calls have significant overhead at the PHP/C boundary. This contradicts how similar FFI/JNI/ctypes interfaces work in other languages where the boundary cost is often negligible. The common mistakes (frequent small calls, not freeing memory, parsing headers per request) all stem from this fundamental misunderstanding of where FFI provides value.
Also Known As
TL;DR
Explanation
PHP FFI (available since PHP 7.4, ext-ffi) lets you declare C function signatures and call them from PHP using FFI::cdef() or FFI::load(). This enables calling libsodium directly, integrating with system libraries, and using high-performance native code for CPU-intensive operations. The trade-off: FFI calls have overhead (no JIT optimisation across the boundary), and incorrect memory management can segfault the process. Best for: wrapping C libraries that don't have a PHP extension, or when an extension cannot be installed.
Common Misconception
Why It Matters
Common Mistakes
- Using FFI for small frequent calls — the overhead per call is substantial; batch operations where possible.
- Not freeing FFI-allocated memory — PHP GC does not manage C memory; use $ffi->free() or CData destructors.
- FFI in web requests — FFI::cdef() parses C headers on every call; cache the FFI instance.
- Not checking if ext-ffi is enabled — it is disabled by default in some distributions.
Code Examples
// Parsing headers on every request — very slow:
function getOsInfo(): string {
$ffi = FFI::cdef('char *getenv(const char *name);'); // Parsed every call
return FFI::string($ffi->getenv('HOME'));
}
// Cache FFI instance — parse headers once:
class NativeLib {
private static ?FFI $ffi = null;
private static function ffi(): FFI {
return self::$ffi ??= FFI::cdef(
'int add(int a, int b);',
'/usr/local/lib/mylib.so'
);
}
public static function add(int $a, int $b): int {
return self::ffi()->add($a, $b);
}
}