The DataStax PHP driver for Cassandra would not compile on PHP 8. That was the whole problem.
At the time I was working at Nano Interactive. The entire backend was PHP. We were running Apache Cassandra as a primary data store — session data, event pipelines, a few hot lookup tables — and the driver that talked to it was the official DataStax extension. It worked fine on PHP 7. Then PHP 8 came out, we needed to upgrade, and the extension refused to build. Compile errors in the Zend internals layer. make spitting out pages of incompatible API warnings.
I went looking for a fix. There was none. DataStax had effectively abandoned the project — no PHP 8 tag, no branch, no response on the open issues. The last meaningful release was years old. The options were: stay on PHP 7 indefinitely, rewrite our Cassandra layer to use something else, or fix the driver ourselves.
why not just switch
Switching looked easy on paper. In practice the codebase had Cassandra-specific types everywhere — Cassandra\Uuid, Cassandra\Timestamp, Cassandra\Bigint — not because of architectural brilliance but because the extension had its own type system and those types had leaked into application code over years. Replacing the driver meant touching hundreds of files. And we had no guarantee that a different client library would give us the same behavioral semantics — retry policies, paging state, consistency levels.
There was also no other option in the PHP ecosystem. No community-maintained pure-PHP client existed that came close in performance — the extension is a thin wrapper over the ScyllaDB C/C++ driver, which means shard-aware routing and native binary protocol handling at C++ speed. A pure-PHP implementation would have been an order of magnitude slower. The realistic alternatives were: stay on PHP 7 until it became a security liability, or abandon PHP entirely and rewrite the service in another language.
Staying on PHP 7 was not a permanent answer. Security patches. Framework compatibility. PHP 7 EOL was already behind us. Rewriting the service was months of work with no guarantee the result would be better. So I forked the driver and started reading Zend source.
learning the zend engine the hard way
PHP extensions are C code that hooks into the Zend Engine through a set of macros and function tables. In principle that is not complicated. In practice the codebase I inherited had been written to support PHP 5, 6, and 7 simultaneously through a layer of compatibility macros with names like PHP5TO7_ZEND_OBJECT, PHP5TO7_ZEND_HASH_FOREACH_STR_KEY_VAL, and PHP5TO7_ZVAL_MAYBE_DESTROY. There were dozens of them, some wrapping trivial one-liners, some hiding real semantic differences between PHP versions.
The Zend Engine's object model changed significantly between PHP 7 and PHP 8. zend_parse_parameters deprecated half its format specifiers. get_properties and get_gc handlers changed signatures. And the macros — originally written to smooth over the 5→7 transition — did not account for 8 at all.
I fixed the compile errors. PHP 8.0 worked. Then 8.1. Then 8.2, which introduced new deprecations of its own — __toString prototype mismatches, zend_hash_sort API changes. Each minor version was another round of "which macro is lying to me now."
This was also when I realized the original driver had been written in C, and that C was making the maintenance problem worse. Not because C is wrong for PHP extensions — it is perfectly valid — but because the compatibility macro layer had produced code that was almost impossible to read. You needed three mental translation steps to understand what any given function actually did at runtime.
the decision i still regret
In March 2023 I renamed every .c file to .cpp. All of them. In one pull request.
The rationale made sense at the time: C++ gives you RAII, references that cannot be null, std::string instead of manual char * arithmetic, better type inference. The Zend API is a C API but you can call it from C++ with extern "C". ScyllaDB's own C/C++ driver — which we wrap — is C++ already. The pieces would fit together.
What I underestimated was how much of the existing code relied on C idioms that do not translate cleanly to C++: implicit void * casts everywhere, VLAs, designated initializers used in ways the C++ standard does not permit. The conversion produced warnings. Some warnings were bugs. Tracking down which was which took months.
And the toolchain story got harder. Finding people who were comfortable contributing to a PHP extension was already difficult. Finding people comfortable with a PHP extension written in C++ that wraps a C++ driver and uses CMake — that narrowed the pool considerably.
I would have been better off keeping C and just deleting the compatibility macro layer one file at a time. The abstraction was the problem, not the language.
cmake and the build system swamp
The original extension used config.m4 — the PHP build system, based on autoconf. It worked, but it was opaque, hard to extend, and did not give you any way to express modern dependency relationships cleanly. Adding the ScyllaDB C/C++ driver as a bundled dependency was painful.
I replaced it with CMake. That decision I do not regret — CMake at least has documentation you can actually read. But learning it was its own project. The PHP extension build model assumes phpize generates your Makefile. CMake generates its own build system and has to replicate what phpize does: finding the PHP headers, setting the correct CFLAGS, linking against the right interpreter library, installing the .so to the right extension directory.
Getting that working took several iterations. Getting it to work on GitHub Actions — across PHP 8.1, 8.2, 8.3, multiple Linux distributions, both thread-safe and non-thread-safe builds — took longer. The CI was a second project hiding inside the first one. There were commits like fix(release): release fixed appearing three times in a row because I had been testing the release workflow by pushing tags and watching it fail.
libuv was its own subplot. The ScyllaDB C++ driver depends on libuv. Building libuv as a static dependency and linking it into a shared .so requires -fPIC everywhere. Miss it in one place — the C++ driver's own cmake build, for instance — and you get a linker error that looks completely unrelated to the actual cause. I have that error memorized.
daniel and he4rt
At some point in 2023, Daniel — danielhe4rt — joined ScyllaDB and started looking at the PHP driver situation. He found the fork, sent a message, and we started working on it together under the he4rt organization.
Working with Daniel changed the pace of the project. He had a different focus — documentation, making the thing actually approachable for people who were not deep in the Zend internals — which complemented the low-level work I was doing. The test suite migrated from Behat to Pest. The README became legible. The he4rt organization became the home for the project.
The driver got PHP 8.3 support in late 2023. PIE packaging in early 2025. macOS and Apple Silicon in 2026. Each of those required fixing something that had been quietly broken for a while.
where it is now
The codebase is functional but not clean. There are still hundreds of PHP5TO7_* macros in the source — the same ones that caused problems in 2022 — because removing them safely requires understanding each one individually and verifying the replacement compiles and behaves correctly across every supported PHP version. We are doing this in staged waves. Wave 1 is done. Wave 2 is in progress.
The PHP stubs — .stub.php files that describe the extension's public API and generate the argument info tables that PHP uses for reflection, named arguments, and IDE support — are about 70% complete. The remaining classes have hand-written arginfo that was correct for PHP 7 and may or may not be correct now.
PHP 8.5 is coming. Some of the GC handler signatures changed again. Those fixes are already in.
what i want to do next
AI tooling has made this kind of mechanical refactoring much faster than it used to be. Working through the macro purge with an AI assistant that understands the Zend API has compressed weeks of careful reading into days. I want to spend more structured time on the project this year — not just fixing what's broken when someone files an issue, but actually finishing the cleanup work that has been sitting in the backlog.
That means completing the stub coverage, removing the remaining PHP5TO7 macros, getting the full test suite green across PHP 8.2 through 8.5, and publishing proper releases on a predictable schedule. The PIE packaging is already there. The GitHub Actions pipeline works. The pieces exist; they need to be connected.
The driver is useful. People are using it — in Serbia, in Brazil, in teams I have never talked to who found it through Packagist. That matters enough to do the job properly.
There is also a series of posts I want to write alongside this work. The PHP extension ecosystem has almost no approachable documentation for people starting from scratch in 2025. Most tutorials assume autoconf, PHP 7, and C89. I want to write about building a PHP extension from zero using C23 and CMake — including the bridge between CMake and the config.m4 build system that phpize expects, which is the part nobody documents clearly. And about publishing it on PIE, the modern PHP extension installer that finally makes distributing compiled extensions sane.
Beyond C, the official stance is that PHP extensions must be written in C or C++. That is technically true — the Zend API is a C API — but it is not the whole story. Rust can call C APIs. Zig can call C APIs. You can write the glue layer in C and implement the actual logic in whatever compiled language you want. I plan to write about that: what it actually takes to build a PHP extension in Rust, in Zig, in anything that can produce a shared library and link against libphp. The ecosystem deserves more than one path.
what it isn't
- It is not an official ScyllaDB product — even though I now work at ScyllaDB. It started as a community fork of an abandoned DataStax project and that is still what it is. Nothing here represents ScyllaDB's roadmap or commitments.
- It does not have a test suite that covers every API surface. Large sections of the schema metadata API are untested.
- It does not have a stable ABI across PHP minor versions. You recompile for each PHP version. This is true of all PHP extensions, but worth stating.
- The C-to-C++ migration added real complexity. Contributing to this extension requires understanding both the Zend C API and C++17. That is a higher bar than it should be.
- The macro cleanup is not done. There are correctness bugs hiding in the legacy layer. We find them by running the test suite, and the test suite does not cover everything yet.
The project is in a better place than it was in 2022. It is not where I want it to be. I am still working on it.
Top comments (0)