Skip to content
happystrawPublic

About

Phpz - Build PHP Extensions with Zig (A Zig framework for building PHP extensions with PHP C API bindings.)

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Latest commit

 

History

274 Commits

Folders and files

Repository files navigation

Phpz - Build PHP Extensions with Zig

A Zig framework for building PHP extensions with PHP C API bindings.

ℹ️ Note: phpz already covers most PHP extension use cases, but the API is still evolving. Pin to a specific commit if you need stability.

Requirements

  • zig: 0.17.0
  • php: 8.2-8.5 (NTS/ZTS) on Linux (x86_64/aarch64), macOS (x86_64/aarch64), Windows MSVC (x64)

Features

  • Modules — extension metadata, lifecycle hooks, phpinfo, INI, globals, observers, and exports.
  • Stub integration — arginfo, function entries, constants, attributes, class metadata, and arginfo checks.
  • Functions and methods — typed Ctx arguments, returns, nullable values, raw zvals, and PHP argument errors. Scalar parameters follow ZPP exactly: weak-mode coercion, strict_types, int-to-float widening, null deprecations and Zend's own error messages.
  • Classes and OOP — bind Zig struct lifecycles to PHP objects, with class entries, methods, inheritance, interfaces, enums, exceptions, handlers, and optional operator overloading and comparison.
  • Runtime state — typed module globals, PHP/Zend globals, and superglobal access for NTS/ZTS builds.
  • Zval and Zend APIs — zval conversions, ownership helpers, arrays (including ListBuilder for ZEND_HASH_FILL_PACKED-style construction and symtable* lookups with PHP key semantics), strings, objects, callables, functions, and resources.
  • Native closures — create PHP Closure values with Function.createClosure(), closure.createFromHandler(), or closure.createFromFn().
  • INI, phpinfo, and errors — typed INI values, phpinfo helpers, PHP errors, exceptions, and bailout-safe cleanup.
  • Observers — function-call, error, and exception hooks for profiling and monitoring.
  • Memory — PHP-backed Zig allocator for any alignment, with optional debug leak traces that are safe under ZTS.
  • Build integration — PHP C translation, extension linking, and platform-specific setup.
  • PHP build system — optional support for built-in and shared extensions. Pass --with-php-build-system to phpz_skel.php; see the skeleton guide.
  • Cross-compilation — build Windows x86_64 (MSVC) extensions from Linux; see scripts/build-windows.sh.

Quick Start

Generate a PHP extension skeleton:

# Generate a new PHP extension skeleton
# PHP >= 8.3:
curl -fsSL https://raw.githubusercontent.com/happystraw/phpz/dev/tools/phpz_skel.php \
  | php -- --ext my_php_extension

# PHP 8.2:
curl -fsSLO https://raw.githubusercontent.com/happystraw/phpz/dev/tools/phpz_skel.php
php phpz_skel.php --ext my_php_extension

# Run tests (Optional)
cd my_php_extension
zig build run-tests

Manual Setup

For a complete minimal project, see examples/skeleton.

1. Add phpz

zig fetch --save git+https://github.com/happystraw/phpz

2. Create the extension files

A phpz extension usually contains:

  • build.zig.zon — declares the phpz dependency
  • build.zig — initializes Phpz and calls phpz.addExtension
  • <name>.stub.php — PHP API declarations
  • <name>_arginfo.h — generated by PHP's gen_stub.php
  • <name>.h — includes phpz.h and the generated arginfo header
  • src/root.zig — registers functions, classes, and the module

3. Generate arginfo

php /path/to/php-src/build/gen_stub.php <name>.stub.php

4. Build

zig build -Dphp-include-dir="$(php-config --include-dir)"

For the complete build.zig, stub.php, header, Zig module, and PHPT tests, use examples/skeleton as the reference.

Examples

Check out the examples/ directory for complete working examples.

How It Works

  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()/functions() → zif_* export
                                ├─ phpz.Class() → wrapper + zim_* exports
                                └─ phpz.module() → get_module() export
                                                             │
                                                             ├─── PHP loads .so
                                                             │
                                                             ▼
                                                        module_startup  [auto]
                                                        ├─ register_{name}_symbols   (constants)
                                                        ├─ ini_entries
                                                        └─ user hook
                                                             └─ Class.register()     (classes) [manual]
  1. Build-time — gen_stub.php generates arginfo.h from .stub.php, containing function metadata and register_* symbols. translate-c converts PHP C headers into Zig bindings.

  2. Compile-time — phpz.function() and phpz.functions() export zif_* wrappers, phpz.Class() creates wrapper types, and phpz.module() exports get_module() for the dynamic loader.

  3. Runtime — PHP loads .so → get_module():

    What Registered by When How
    Functions ext_functions Module init Auto: set in module entry struct
    Constants register_{name}_symbols MINIT Auto: called by module_startup_func
    Classes register_class_* MINIT Auto: .classes of phpz.module, in the given order

    Functions are resolved from the module entry when the extension loads; constants are registered during MINIT via the auto-generated register_{name}_symbols. Classes listed in .classes are registered in order before module_startup_fn, so list parents and interfaces first; use a class's .register option to supply them.

Threads

Set .tsrmls_cache_tcb_fast_path = true in the options passed to Phpz.init to enable direct TCB access to PHP's TSRM cache on Linux x86_64/aarch64 ZTS builds. It defaults to false, which disables both TCB offset detection and direct thread-pointer reads. When disabled or unavailable, access uses the static cache by default, or runtime lookup with .static_tsrmls_cache = false. The two options are independent of LTO; NTS builds ignore both TSRM options.

Zend APIs, zvals, php_allocator and request memory belong to the PHP thread that is running the request. Native worker threads must not touch them, under NTS or ZTS.

Bailouts

A Zend bailout uses longjmp: Zig defer and errdefer between the bailout and the boundary that catches it do not run.

  • php_allocator uses PHP's fallible allocation API when available, or catches the immediate OOM bailout on older PHP versions. Both paths return error.OutOfMemory, allowing the caller's Zig cleanup to run. Propagate the error: phpz handlers process a queued OOM or resume the earlier bailout. With delayed OOM, PHP leaves UnwindExit pending when an execution frame is available; otherwise it bails out.
  • The try* APIs (tryCall, tryNew, tryEval, ...) convert an escaping bailout to error.ZendBailout. Cleanup outside that boundary can run; cleanup skipped inside it is not replayed. These APIs can also return error.UnwindExit for exit/die or delayed OOM. Propagate these errors after native cleanup; standalone hosts must finish request shutdown.
  • Other Zend calls can bail out directly. Keep native resources that must be released on bailout in a GuardCtx scope, or in objects PHP destroys at request shutdown.
  • An error.OutOfMemory without an existing PHP OOM, such as a native allocation failure, becomes a PHP fatal "Out of memory" error.
  • On PHP versions with a hard memory limit, a non-fallible allocation that would exceed it terminates the process directly, without Zig cleanup or PHP request shutdown.

Acknowledgements

phpz was inspired by ext-php-rs.

License

MIT License.

About

Phpz - Build PHP Extensions with Zig (A Zig framework for building PHP extensions with PHP C API bindings.)

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages