Module Globals
PHP extensions can declare per-module global state that is automatically managed by the engine. In ZTS (thread-safe) builds, PHP’s TSRM allocates a separate copy of the globals for each thread. In non-ZTS builds, the globals are a plain static variable.
ext-php-rs exposes this via ModuleGlobals<T> and the ModuleGlobal trait.
Defining Globals
Create a struct that implements Default and ModuleGlobal:
use ext_php_rs::zend::{ModuleGlobal, ModuleGlobals};
#[derive(Default)]
struct MyGlobals {
request_count: i64,
max_depth: i32,
}
impl ModuleGlobal for MyGlobals {
fn ginit(&mut self) {
self.max_depth = 512;
}
}
Default::default() initializes the struct, then ginit() runs for any
additional setup. In ZTS mode these callbacks fire once per thread; in non-ZTS
mode they fire once at module load.
If you don’t need custom initialization, leave the trait impl empty:
use ext_php_rs::zend::{ModuleGlobal, ModuleGlobals};
#[derive(Default)]
struct SimpleGlobals {
counter: i64,
}
impl ModuleGlobal for SimpleGlobals {}
Registering Globals
Declare a static and pass it to ModuleBuilder::globals():
use ext_php_rs::prelude::*;
use ext_php_rs::zend::{ModuleGlobal, ModuleGlobals};
#[derive(Default)]
struct MyGlobals { request_count: i64, max_depth: i32 }
impl ModuleGlobal for MyGlobals {}
static MY_GLOBALS: ModuleGlobals<MyGlobals> = ModuleGlobals::new();
#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module.globals(&MY_GLOBALS)
}
Only one globals struct per module is supported (a PHP limitation).
Accessing Globals
Use get() for shared access and get_mut() for mutable access:
use ext_php_rs::prelude::*;
use ext_php_rs::zend::{ModuleGlobal, ModuleGlobals};
#[derive(Default)]
struct MyGlobals { request_count: i64, max_depth: i32 }
impl ModuleGlobal for MyGlobals {}
static MY_GLOBALS: ModuleGlobals<MyGlobals> = ModuleGlobals::new();
#[php_function]
pub fn get_request_count() -> i64 {
MY_GLOBALS.get().request_count
}
#[php_function]
pub fn increment_request_count() {
unsafe { MY_GLOBALS.get_mut() }.request_count += 1;
}
get() is safe because PHP runs one request per thread at a time. get_mut()
is unsafe because the caller must ensure exclusive access (which is guaranteed
within a single #[php_function] handler, but not from background Rust threads).
Advanced: Raw Pointer Access
For power users who need direct pointer access (e.g., passing to C APIs or
building custom lock-free patterns), as_ptr() returns *mut T:
use ext_php_rs::zend::{ModuleGlobal, ModuleGlobals};
#[derive(Default)]
struct MyGlobals { request_count: i64 }
impl ModuleGlobal for MyGlobals {}
static MY_GLOBALS: ModuleGlobals<MyGlobals> = ModuleGlobals::new();
let ptr: *mut MyGlobals = MY_GLOBALS.as_ptr();
Cleanup
Implement gshutdown() if your globals hold external resources:
use ext_php_rs::zend::ModuleGlobal;
#[derive(Default)]
struct MyGlobals { handle: Option<u64> }
impl ModuleGlobal for MyGlobals {
fn gshutdown(&mut self) {
self.handle.take();
}
}
The struct is also dropped after gshutdown() returns, so standard Drop
implementations work as expected.