v0.4.3
Highlights
Callbacks: pass a PHP callable where a C function pointer is expected.
Until now every function-pointer parameter was generated as an opaque void *, so callback-taking C APIs (comparators, write/visit callbacks, and similar) couldn't actually be driven from PHP — PHP FFI rejects a closure for a void *. They are now generated as real C callback types, so a PHP closure or named callable is passed straight through and PHP FFI builds the C callback trampoline.
What you can do now
// int example_apply(int value, int (*callback)(int));
$result = Example::example_apply(20, static fn (int $v): int => $v * 2); // 41- Generated wrappers type a function-pointer parameter as
callable(thecdatavariant also accepts\FFI\CData). - The callback's arguments and return value are marshalled from the C signature in the cdef — an
intarrives as a PHPint, a pointer arrives as an\FFI\CData. - Both a closure and a plain callable (a function name) are accepted.
Faithful, typed callback signatures
Each component of the callback keeps its real, declared type, so your closure receives something useful:
- A pointer to a type the package already emits (its own
struct/typedef) stays that type — e.g.int (*cb)(const struct point *)— so the closure gets a typed\FFI\CDataand can read fields ($p->x) without a cast. - A
const char *callback argument stays achar *, readable with\FFI::string(). - Scalars keep their exact C width.
- A component degrades to
void *only when its spelling can't be placed in a function-pointer declarator (a nested function pointer, an array, or an anonymous aggregate); the whole parameter falls back tovoid *for a pointer-to-function-pointer or a by-value aggregate. A type referenced only through a callback signature is backfilled, so the cdef still loads.
Scope & caveats (PHP FFI itself)
- Synchronous callbacks — invoked while the C call is on the stack (the common case) — are fully supported.
- A stored / asynchronous callback (kept by C and called after the call returns) inherits PHP FFI's own trampoline-lifetime limitation and is not guaranteed safe.
- Throwing out of a callback is fatal (
Throwing from FFI callbacks is not allowed) — handle errors inside the callback and return a value.
Validation
New unit coverage for callback rendering and the backfill extractor, plus an end-to-end test that invokes both a closure and a named callable through the compiled native fixture. Generated golden snapshots updated. Rust (154) and PHP (62) suites green; clippy, php-cs-fixer, and phpstan clean.