A Zig framework for building PHP extensions with PHP C API bindings.
ℹ️ Note: This is a personal experimental project — functionality works well for its intended use cases, but expect API changes as it evolves. Pin to a specific commit if you need stability.
- zig: 0.16.0
- php: 8.2-8.5 (tested on linux/macos)
- Module —
phpz.module()w/module_startup,module_shutdown,request_startup,request_shutdown,infolifecycle hooks - Class & OOP —
phpz.Class(Zigextern struct↦ PHP class,init/deinit),phpz.SimpleClass(interfaces, traits, enums, exceptions), methods, properties (static & instance), inheritance viaregister()hook - Functions —
phpz.function()with type-safeCtx.Call.parse(),Ctxreturn values,$this/ scope access - Constants — global and class constants via
constin stub files, auto-registered during MINIT byregister_{name}_symbols - Type-safe Zval — checked conversions (
is/as/asOrDefault),Zval.Array/Zval.Objectbuilders,Zval.Optionalnullable params,Zval.nativeraw pointer ops - Zend APIs —
zend.Array(HashTable CRUD, iterators, sort),zend.String(concat, hash),zend.Object(properties, calls, enum),zend.ClassEntry,zend.Function,zend.Callable(type-safe fci/fcc),zend.PropertyInfo,zend.Reference,zend.Resource - INI Settings —
php.inidirectives with on-update callbacks and runtime getters - Error Handling — PHP error triggers, exception throwing, argument validation errors
- Memory — Zig
Allocatorbacked by PHP'semalloc, with DWARF leak tracing for debug builds - Auto-Registration — stub-file-driven: functions (
ext_functions) and constants (constin stub →register_{name}_symbols) are automatically registered at module startup; classes require an explicitClass.register()call inmodule_startup_fn
💡 Tip: For complete working examples, see the
examples/directory.
Add phpz to your build.zig.zon
zig fetch --save git+https://github.com/happystraw/phpzFor PHP 8.0+, it's recommended to use stub files to generate arginfo headers:
Create a stub file (e.g., my_php_extension.stub.php):
<?php
/**
* @generate-class-entries
* @undocumentable
*/
function hello_world(): void {}
function my_function(string $name, int $age = 0): string {}Generate the arginfo header using PHP's gen_stub.php:
php /path/to/php-src/build/gen_stub.php my_php_extension.stub.phpThis will generate my_php_extension_arginfo.h containing all the necessary argument info definitions (functions, classes, constants, etc.).
Create a C header file (e.g., my_php_extension.h):
phpz.h provides the core C API needed for building PHP extensions (includes
php.h,Zend/zend_API.hetc.).Include it in your extension header to access all necessary PHP C APIs.
#include "phpz.h"
#include "my_php_extension_arginfo.h"
// ... other header filesConfigure your build.zig:
// Import the Phpz build system module
const Phpz = @import("phpz").Phpz;
// Fetch the phpz dependency declared in build.zig.zon
const phpz_dep = b.dependency("phpz", .{});
// Initialize Phpz: translates PHP C headers into Zig bindings
const phpz = Phpz.init(phpz_dep, .{
.target = target,
.optimize = optimize,
// C header for translate-c
.c_source_file = b.path("my_php_extension.h"),
// Directory containing PHP header files (main/, Zend/, TSRM/, ext/).
.php_include_dir = .{ .cwd_relative = "/usr/include/php" },
});
// Import the phpz module into your extension library
lib.root_module.addImport("phpz", phpz.mod);
// Apply OS-specific linker settings (macOS undefined symbols, Windows php8.lib)
phpz.apply(lib);Then zig build !
Check out the examples/ directory for complete working examples.
Build-time Compile-time Runtime
────────── ──────────── ───────
stub.php Zig source
│ │
│ php-src/build/gen_stub.php │
▼ │
arginfo.h │
[ext_functions] │
[register_class_*] │
[register_{name}_symbols] │
│ │
│ translate-c (zig build) │
▼ ▼
PHP C bindings ─────────────► phpz comptime:
(php_c module) ├─ phpz.function() → zif_* export
├─ Class.method() → zim_* export
├─ phpz.Class(T) → wrapper type
└─ phpz.module() → get_module() export
│
├─── PHP loads .so
│
▼
module_startup [auto]
├─ register_{name}_symbols (constants)
├─ ini_entries
└─ user hook
└─ Class.register() (classes) [manual]
-
Build-time —
gen_stub.phpgeneratesarginfo.hfrom.stub.php, containing function metadata andregister_*symbols.translate-cconverts PHP C headers into Zig bindings. -
Compile-time —
phpz.function()exportszif_*wrappers,phpz.Class()creates wrapper types,phpz.module()exportsget_module()for the dynamic loader. -
Runtime — PHP loads
.so→get_module():What Registered by When How Functions ext_functionsModule init Auto: set in module entry struct Constants register_{name}_symbolsMINIT Auto: called by module_startup_funcClasses register_class_*MINIT Manual: Class.register()inmodule_startup_fnFunctions are resolved from the module entry when the extension loads; constants are registered during MINIT via the auto-generated
register_{name}_symbols. Classes require an explicitClass.register()call inmodule_startup_fn— this gives you control over registration order and the opportunity to configureObjectHandlersor parent classes.