article thumbnail
Understanding PHP FFI
A deep dive into PHP's Foreign Function Interface (FFI)
#

Understanding PHP FFI: A Practical Introduction

Foreign Function Interface (FFI) lets PHP call C functions and read/write C data structures directly from userland. Instead of waiting for a PHP extension to be compiled and installed, you can bind to an existing shared library (.so, .dylib, .dll) at runtime and use it immediately.

This unlocks three big wins:

⚠️ Safety: FFI can crash the PHP process if you misdeclare a function or misuse pointers. Use it thoughtfully in CLI tools and background workers first; restrict or disable in shared web runtimes.


How FFI Works (in 60 seconds)

  1. You describe C types & function signatures in a C definition string: FFI::cdef($cHeader, $libName).
  2. PHP loads the shared library and returns an FFI handle that exposes those functions and types as PHP callables/objects.
  3. You allocate C memory (FFI::new, FFI::array), pass pointers to functions, and convert results back to PHP with helpers like FFI::string().

Example 1 -- Read Linux system memory with sysinfo(2) (not a built‑in PHP function)

PHP doesn't expose the full sysinfo syscall, but the C library does. With FFI you can read uptime, loads, RAM and swap totals in one call.

<?php
// sysinfo: declared in <sys/sysinfo.h> and provided by libc
$ffi = FFI::cdef(<<<'CDEF'
typedef long time_t;
typedef unsigned long __kernel_ulong_t;

struct sysinfo {
    long uptime;             // Seconds since boot
    unsigned long loads[3];  // 1, 5, 15 minute load averages (fixed‑point 1/65536)
    unsigned long totalram;  // Total usable main memory size
    unsigned long freeram;   // Available memory size
    unsigned long sharedram; // Amount of shared memory
    unsigned long bufferram; // Memory used by buffers
    unsigned long totalswap; // Total swap space size
    unsigned long freeswap;  // Swap space still available
    unsigned short procs;    // Number of current processes
    unsigned long totalhigh; // Total high memory size
    unsigned long freehigh;  // Available high memory size
    unsigned int mem_unit;   // Memory unit size in bytes
    char _f[20-2*sizeof(long)-sizeof(int)]; // Padding to 64 bytes
};

int sysinfo(struct sysinfo *info);
CDEF, "libc.so.6");

$info = $ffi->new("struct sysinfo");
if ($ffi->sysinfo(FFI::addr($info)) !== 0) {
    throw new RuntimeException("sysinfo() failed");
}

$scale = 65536.0;
$load1  = $info->loads[0] / $scale;
$load5  = $info->loads[1] / $scale;
$load15 = $info->loads[2] / $scale;
$bytes  = fn(int $n) => $n * $info->mem_unit;

printf(
    "Uptime: %d s, Loads: %.2f, %.2f, %.2f\nRAM: %.1f GB total, %.1f GB free\n",
    $info->uptime, $load1, $load5, $load15,
    $bytes($info->totalram)/1e9, $bytes($info->freeram)/1e9
);

Why this is useful: a single, fast syscall gives you host health without shelling out or parsing /proc files.


Example 2 -- Special math with erf/erfc from libm (not in core PHP)

PHP lacks error function helpers common in stats/signal processing. Bind to the math library instead:

<?php
$ffi = FFI::cdef(<<<'CDEF'
double erf(double x);
double erfc(double x);
CDEF, "libm.so.6");

$z = 1.0;
printf("erf(%f) = %.6f\n", $z, $ffi->erf($z));
printf("erfc(%f) = %.6f\n", $z, $ffi->erfc($z));

Use cases: Gaussian CDFs, diffusion models, and numerical methods that rely on erf/erfc.


From Library to Correct cdef: Using nm/readelf/objdump

When a function isn't documented in PHP, you need to confirm its symbol name and signature. Three handy tools on Linux:

Workflow

  1. Locate the library

    ldconfig -p | grep -E 'libm|libc|yourlib'
  2. List symbols

    nm -D /lib/x86_64-linux-gnu/libm.so.6 | grep -E 'erf|erfc'
    # or:
    readelf -Ws /lib/x86_64-linux-gnu/libm.so.6 | grep erf

    You'll see something like _Z... for C++ (mangled) or plain erf. Prefer C symbols (unmangled).

  3. Confirm the signature in headers/man pages

    • Check man 3 erf, or read /usr/include/math.h to copy the declaration exactly.
    • Paste that declaration into your FFI::cdef(...) block.
  4. Test with safe inputs and add runtime guards in PHP.

Tip: for complex structs, copy the exact C struct from the header into your cdef. Field order and sizes must match your platform's headers.


Portability Notes


Key Takeaways

If PHP has felt limited before, FFI will change how you think about "what PHP can do." It's a bridge to native power--use it wisely and it will make your applications feel faster and more capable.

Ready to dive deeper? Check out our advanced section