*All PHP versions -sys crate
This crate is meant to facilitate creation of custom PHP extensions in Rust. It provides C bindings for all supported PHP versions.
This crate went through a few iterations, for now its a single crate containing all PHP versions as separate modules - this could change in the future.
In the beginning it also was generating Rust bindings on demand. But currently bindings are meant to be pregenerated - to test if the same crate works on OSX and Windows - without having to vendor OSX and Windows PHP headers.
Its possible we'll need to vendor OSX and Windows PHP includes to fully support these platforms.
Support
| PHP Version | Linux | Windows | OSX | ZTS Linux | ZTS Windows |
|---|---|---|---|---|---|
| PHP 5.4 | Yes | ||||
| PHP 5.5 | |||||
| PHP 5.6 | Yes | Yes | |||
| PHP 7.0 | Yes | ||||
| PHP 7.1 | Yes | ||||
| PHP 7.2 | Yes | ||||
| PHP 7.3 | Yes | ||||
| PHP 7.4 | Yes | ||||
| PHP 8.0 | Yes | ||||
| PHP 8.1 | Yes | ||||
| PHP 8.2 | Yes | ||||
| PHP 8.3 | Yes | ||||
| PHP 8.4 | Yes | ||||
| PHP 8.5 | Yes |
Example extension — one .so that loads on every PHP 8.x
example-ext/ is a complete, loadable PHP extension that is
built once into a single .so and loads unchanged into every supported
PHP 8.x (8.0–8.5). It exports two userland functions and a phpinfo() section:
When PHP loads a .so it validates two fields of the module entry byte-for-byte
— the Zend module API number (==) and the build id (strcmp) — so a module
with those values baked in at compile time loads on exactly one PHP minor.
Instead of baking them in, this extension reads them from the host at load
time: PHP registers its built-in modules (ext/standard, Core, …) into the
exported module_registry before it loads any external extension, and each one
carries the running engine's exact zend_api, build_id, zts, zend_debug
and size. get_module() reads one of them and echoes those fields, so the
load-gate matches on every version — NTS/ZTS/debug suffixes handled for free.
The only ABI shape that actually changed across 8.x is zend_function_entry
(32 bytes on 8.0–8.3, 48 bytes on 8.4+, which appended two fields); its stride
is picked at runtime from the detected API number. Every other type the code
touches is byte-identical across 8.x, so a single php-all-sys binding is
ABI-correct for all of them and the code never names a concrete version.
Build it once and load it into any PHP 8.x:
CI proves the claim: a build-universal job compiles the .so once and
uploads it, then a load matrix over PHP 8.0–8.5 downloads that same artifact,
loads it into each real PHP, and runs example-ext/tests/smoke.php
— which asserts the extension loaded and that the Zend API it detected is the one
the running PHP actually uses.
Generating bindings
The cargo-php-sys-build crate can be installed and used to generate binding files.