Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.