ext-php-rs 0.15.10

Bindings for the Zend API to build PHP extensions natively in Rust.
Documentation
# Custom SAPI

ext-php-rs allows you to implement a fully custom SAPI (Server API) in Rust
using the `Sapi` trait. This lets you embed the PHP engine inside your own
server, such as an HTTP framework, a message queue consumer, or any other
application that needs to drive PHP request processing.

## Overview

A SAPI implementation consists of three parts:

1. **`ServerContext`** -- per-request state (method, URI, POST body, headers).
2. **`Sapi`** -- the SAPI itself: output, logging, header sending.
3. **Trampolines** -- generated automatically by `Sapi::build_module()`.

## Defining a `ServerContext`

Implement the `ServerContext` trait for your per-request type:

```rust,ignore
use ext_php_rs::embed::{ServerContext, RequestInfo};

struct MyContext {
    method: String,
    uri: String,
    body: Vec<u8>,
    body_offset: usize,
    finished: bool,
}

impl ServerContext for MyContext {
    fn init_request_info(&self, info: &mut RequestInfo) {
        info.request_method = Some(self.method.clone());
        info.request_uri = Some(self.uri.clone());
        info.content_length = self.body.len() as i64;
    }

    fn read_post(&mut self, buf: &mut [u8]) -> usize {
        let remaining = &self.body[self.body_offset..];
        let n = buf.len().min(remaining.len());
        buf[..n].copy_from_slice(&remaining[..n]);
        self.body_offset += n;
        n
    }

    fn read_cookies(&self) -> Option<&str> {
        None
    }

    fn finish_request(&mut self) -> bool {
        if self.finished {
            return false;
        }
        self.finished = true;
        true
    }

    fn is_request_finished(&self) -> bool {
        self.finished
    }
}
```

## Implementing the `Sapi` trait

```rust,ignore
use ext_php_rs::embed::{Sapi, ServerContext, RequestInfo, ServerVarRegistrar};

# struct MyContext { method: String, uri: String, body: Vec<u8>, body_offset: usize, finished: bool }
# impl ServerContext for MyContext {
#     fn init_request_info(&self, _: &mut RequestInfo) {}
#     fn read_post(&mut self, _: &mut [u8]) -> usize { 0 }
#     fn read_cookies(&self) -> Option<&str> { None }
#     fn finish_request(&mut self) -> bool { true }
#     fn is_request_finished(&self) -> bool { true }
# }
struct MySapi;

impl Sapi for MySapi {
    type Context = MyContext;

    fn name() -> &'static str { "my-sapi" }
    fn pretty_name() -> &'static str { "My Custom SAPI" }

    fn ub_write(_ctx: &mut MyContext, buf: &[u8]) -> usize {
        // Forward output to your HTTP response
        print!("{}", String::from_utf8_lossy(buf));
        buf.len()
    }

    fn log_message(msg: &str, _syslog_type: i32) {
        eprintln!("[php] {msg}");
    }
}
```

## Building and starting the SAPI

```rust,ignore
use ext_php_rs::embed::Sapi;
# struct MySapi;
# impl Sapi for MySapi {
#     type Context = ();
#     fn name() -> &'static str { "x" }
#     fn pretty_name() -> &'static str { "x" }
#     fn ub_write(_ctx: &mut (), _buf: &[u8]) -> usize { 0 }
#     fn log_message(_: &str, _: i32) {}
# }
# impl ext_php_rs::embed::ServerContext for () {
#     fn init_request_info(&self, _: &mut ext_php_rs::embed::RequestInfo) {}
#     fn read_post(&mut self, _: &mut [u8]) -> usize { 0 }
#     fn read_cookies(&self) -> Option<&str> { None }
#     fn finish_request(&mut self) -> bool { true }
#     fn is_request_finished(&self) -> bool { true }
# }

let module = MySapi::build_module().expect("failed to build SAPI module");
```

The returned `SapiModule` can then be passed to `sapi_startup()` and
`php_module_startup()` just like a manually-built one.

## Registering `$_SERVER` variables

Override `register_server_variables` in the `Sapi` trait to populate
`$_SERVER`:

```rust,ignore
use ext_php_rs::embed::{Sapi, ServerVarRegistrar};

# struct MySapi;
# struct Ctx;
# impl ext_php_rs::embed::ServerContext for Ctx {
#     fn init_request_info(&self, _: &mut ext_php_rs::embed::RequestInfo) {}
#     fn read_post(&mut self, _: &mut [u8]) -> usize { 0 }
#     fn read_cookies(&self) -> Option<&str> { None }
#     fn finish_request(&mut self) -> bool { true }
#     fn is_request_finished(&self) -> bool { true }
# }
# impl Sapi for MySapi {
#     type Context = Ctx;
#     fn name() -> &'static str { "x" }
#     fn pretty_name() -> &'static str { "x" }
#     fn ub_write(_: &mut Ctx, b: &[u8]) -> usize { b.len() }
#     fn log_message(_: &str, _: i32) {}
    fn register_server_variables(
        _ctx: &mut Ctx,
        registrar: &mut ServerVarRegistrar,
    ) {
        registrar.register("SERVER_SOFTWARE", "my-server/1.0");
        registrar.register("SERVER_PROTOCOL", "HTTP/1.1");
    }
# }
```