# phpx: a C++ wrapper for the Zend API, with a generator for the whole extension

> phpx is the Swoole project's C++ wrapper for the Zend engine, and what makes it different from hand-writing an extension is the surrounding toolchain: phpx init scaffolds a CMake project, a stub file, a smoke test and the arginfo generation, and phpx build, install and enable handle compiling and registering the module. The requirement is PHP 8.4 or later and a C++17 compiler.

**swoole/phpx** — C++ wrapper for Zend API

- Repository: https://github.com/swoole/phpx
- Stars: 871 · Forks: 110
- Language: C++
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/swoole-phpx

## The wrapper, and what it requires

phpx is a C++ wrapper for the Zend API, from the Swoole project, and its goal is to let you write a PHP extension in C++ with type safety and a modern API rather than with the raw zend structures. The requirements are PHP 8.4 or later, Linux, macOS or Windows, GCC 9 or later with C++17 support, and Composer. The primary language of the repository is C++, the licence is Apache-2.0, the last push was on 2026-10-20, and the recent releases are v2.9.4, v2.9.3 and v2.9.2 from September and October 2026. The build produces a shared library, libphpx.so, and the project is developed under a unit test workflow with codecov coverage tracking.

## Building the library, and what debug mode changes

The library itself is built with CMake and the standard build is three commands:

```shell
# Standard build (Release mode)
cmake .
make -j 4
sudo make install
sudo ldconfig
```

The ldconfig step matters on a Linux system because the extension is loaded as a shared object and the dynamic linker caches the path. There is a separate debug flow for troubleshooting, which starts by cleaning the previous build, then configures with a debug build type, then compiles and installs:

```shell
# Clean previous builds
cmake --build . --target clean

# Configure Debug mode (includes debug symbols and runtime checks)
cmake -DCMAKE_BUILD_TYPE=Debug .

# Compile
make -j 4
sudo make install
sudo ldconfig
```

The readme lists what debug mode gives you, and the list is worth knowing before you decide to use it. It generates complete debug symbols, disables compiler optimisations so the debugger is easier to work with, enables runtime error checking, and produces more detailed compilation output. The last of those costs you build time, so it is a troubleshooting setting rather than a default.

## The generator creates the project, the stub and the test

The tool that makes this different from writing an extension by hand is phpx init, and the flow starts in an empty directory:

```shell
mkdir test
cd test
composer require swoole/phpx
vendor/bin/phpx init
```

What init creates is described precisely. It makes the CMake project, the src directory, the include directory, a PHPT smoke test and a README in the current Composer project. It also copies the matching gen_stub.php into the build directory and the run-tests.php script to the project root, and it does all this without requiring phpize, which is the traditional tool for building a PHP extension and the thing this replaces. There is a detail about selecting the PHP installation, because init uses php-config from your PATH, and if that is the wrong one you can point it at a specific path with a flag on init or switch later with the switch command.

## Stubs, arginfo and the rule about headers

The type information is generated rather than written. Stub files belong in the src directory with a .stub.php suffix, and the generated arginfo header is written next to its stub and remains a private implementation file. The build and install cycle is:

```shell
composer require swoole/phpx
vendor/bin/phpx init
vendor/bin/phpx build
sudo vendor/bin/phpx install
sudo vendor/bin/phpx enable
```

The install step copies the module and updates php.ini, and the enable and disable commands do the same work in reverse. The rule about headers is the one to get right, because it decides whether other people can build against your extension. The install command publishes only the C and C++ headers that you place explicitly under the include directory, and it puts them in the extension subdirectory of the php-config include directory, named after the extension. Headers under the src directory are never installed. So if you want to ship a reusable extension, the public headers go in include, and if you leave them in src they are private to your build.

## The API is objects and methods, not zend structs

The key features list names the shape of the API. There are PHPX_METHOD and PHPX_FUNCTION macros for cleaner code, an Extension and Class API for object-oriented extension registration, lambda callbacks for lifecycle management with onStart and onShutdown, type-safe wrappers in the form of Variant, Array, Object and String classes, generic calls to invoke PHP functions and methods through php::call and Object::call, and auto-generated arginfo produced by gen_stub.php. The example in the readme shows all of it working together. A class method is defined with the macro, object properties are read with a get on the this pointer and set with a set, arguments arrive in an args array, and the return value is a plain C++ value. The function form uses a Variant as a universal container, an Array for the array type, an Object for an instance, and the generic call to reach PHP built-ins, so file_get_contents and a method call on a DateTime object are both one line.

## Registering the extension and loading it

The entry point uses the PHPX_EXTENSION macro and constructs an Extension with a name and a version, and then does its registration in the onStart callback. Constants are registered with a name and a value, a class is created with its properties declared as public and with a default, its method table comes from the generated arginfo header, and the class is registered with the extension. The function table is registered separately from the stub generated one, and the info page is filled in with the author, the version and a project link. After you build the extension in its own directory with cmake, make and make install, you load it by editing php.ini, where the line is:

```ini
extension=test.so
```

The smoke test that init generates is a PHP file that calls the function you defined and echoes the result, so a working extension prints its expected output on the first run and the test doubles as the example in the readme.

## The repository reaches beyond a plain extension

The tree shows how much more than a single wrapper this is. There is an include directory for the public headers, a src directory, an sdk directory, a thirdparty directory, a tests directory with a phpunit configuration, and directories that are not about extensions at all: android, ios and a wasm directory with a build-wasm script, a sapi-static directory and a full-static directory, both of which suggest statically linked builds for those targets. There is a bin directory and a format.sh alongside a .clang-format configuration and an editorconfig, so the C++ style is enforced by tooling. The examples directory shows the range of things people have built with it, a bloom filter, an embed example, a gtk example, a queue example, a rocksdb example and one named for variant destruction, which is the kind of example that tells you where the sharp edges are. The documentation and a Chinese readme sit alongside the English one, and the project keeps a CLAUDE.md for coding agents.

## Conclusion

phpx suits a PHP developer who needs to write a native extension and wants the arginfo, the build and the module registration handled rather than assembled by hand from a phpize workflow. It does not suit a project that can be written in userland PHP, and it does not suit someone on PHP 8.3 or older, since the requirement is 8.4 or later. Before you start, check your PHP version and compiler, and read the header installation rule, because only headers placed explicitly under include are published, and headers under src are never installed, which is the difference between a reusable extension and one that only builds on your own machine.

## FAQ

### What are the requirements for building a PHP extension with phpx?

PHP 8.4 or later, Linux, macOS or Windows, GCC 9 or later with C++17 support, and Composer. The library itself is built with CMake, and the shared object is loaded as a PHP module.

### What does phpx init create in my project?

The CMake project, the src and include directories, a PHPT smoke test and a README, and it copies the matching gen_stub.php into the build directory and run-tests.php to the project root, all without requiring phpize. It uses php-config from your PATH, which you can point elsewhere with a flag.

### How are types and arginfo handled in phpx?

You write stub files under src with a .stub.php suffix, and the generated arginfo header is written next to its stub and stays a private implementation file. The extension registers its function table from that generated header, and the classes use an Extension and Class API with public properties.

### How do I install and load the extension after building it?

The phpx install, phpx enable and phpx disable commands copy the module and update php.ini, and the install command publishes only the headers placed explicitly under the include directory, while headers under src are never installed. You then load it in php.ini with an extension line naming the shared object.

## Sources

- [Issues](https://github.com/swoole/phpx/issues)
- [License: Apache-2.0](https://github.com/swoole/phpx/blob/master/LICENSE)
- [README](https://github.com/swoole/phpx/blob/master/README.md)
- [Releases](https://github.com/swoole/phpx/releases)
- [swoole/phpx on GitHub](https://github.com/swoole/phpx)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/swoole-phpx
