PHP Functions
OxPHP registers its functions through the oxphp_sapi extension, which loads automatically for every PHP script the server executes. No extension= directive and no manual loading are required. Every function listed here is available from the first line of your PHP code.
Table of Contents
- oxphp_http_request()
- oxphp_superglobals_enabled()
- oxphp_request_id()
- oxphp_worker_id()
- oxphp_server_info()
- oxphp_finish_request()
- oxphp_is_worker()
- oxphp_worker()
- oxphp_is_streaming()
- oxphp_stream_flush()
- oxphp_sleep()
- oxphp_usleep()
- oxphp_async()
- oxphp_async_await()
- oxphp_async_await_all()
- oxphp_async_await_race()
- oxphp_async_await_any()
- oxphp_register_decorator()
- oxphp_apm_trace()
- oxphp_apm_start()
- oxphp_apm_end()
- oxphp_apm_attribute()
- oxphp_apm_event()
- oxphp_apm_error()
- oxphp_apm_status()
- oxphp_apm_trace_id()
- oxphp_apm_span_id()
- oxphp_apm_header()
- OxPHP\Profile\is_active()
- OxPHP\Profile\start()
- OxPHP\Profile\stop()
- OxPHP\Profile\pause()
- OxPHP\Profile\resume()
- OxPHP\Profile\mark()
- OxPHP\Profile\metric()
- Classes and Interfaces
- Exceptions
oxphp_http_request()
oxphp_http_request(): \OxPHP\Http\RequestReturns a request object for the current HTTP request. Each call returns a new one. The object provides typed access to the HTTP method, URI, query parameters, parsed body, headers, cookies, uploaded files, client IP, and request timing.
Returns: An \OxPHP\Http\Request instance backed by the request data in the current PHP worker thread.
Throws: An exception from the OxPHP\Http\Exception namespace when called outside an active request:
| Exception | Situation |
|---|---|
\OxPHP\Http\Exception\WorkerIdleException |
Worker mode, between requests |
\OxPHP\Http\Exception\AsyncContextException |
Inside an oxphp_async() callback |
\OxPHP\Http\Exception\NoActiveRequestException |
Any other context without an active request |
In normal request-handling code, no exception handling is required.
Example:
<?php
$request = oxphp_http_request();
$method = $request->method(); // "POST"
$path = $request->path(); // "/api/users"
$email = $request->payload('email'); // from JSON or form body
$token = $request->header('Authorization');
$theme = $request->cookie('theme', 'light');For the complete interface reference, see the HTTP Request API documentation.
oxphp_superglobals_enabled()
oxphp_superglobals_enabled(): boolReturns whether superglobal population is enabled for this server instance. The value reflects the SUPERGLOBALS_ENABLED environment variable and does not change during the server's lifetime.
When false, a request runs with $_GET empty and with $_SERVER holding only the four keys PHP registers itself: REQUEST_TIME, REQUEST_TIME_FLOAT, argc and argv. $_POST, $_FILES and $_COOKIE are built as usual, because they come from the request body and the Cookie header, which the setting does not touch. The HTTP Object API (oxphp_http_request()), php://input, and PHP session functions are unaffected. See SUPERGLOBALS_ENABLED for the full table.
Returns: true when SUPERGLOBALS_ENABLED is true (the default), false otherwise.
Example:
<?php
if (oxphp_superglobals_enabled()) {
$query = $_GET['page'] ?? 1;
} else {
$query = oxphp_http_request()->query('page', 1);
}oxphp_request_id()
oxphp_request_id(): stringReturns the unique request identifier for the current request. This is the same value sent in the X-Request-ID response header. If the client sends an X-Request-ID header, OxPHP passes it through unchanged instead of generating a new one.
Returns: A 20-character hexadecimal string when OxPHP generates the ID (e.g. "67890abc12341a2b0042"). When the client sends an X-Request-ID header, that value is returned as-is (1 to 64 characters, alphanumeric plus -, _, .).
Example:
<?php
$id = oxphp_request_id();
error_log("[$id] Processing order #1234");
// Propagate the ID to downstream services
header("X-Correlation-ID: $id");oxphp_worker_id()
oxphp_worker_id(): intReturns the zero-based index of the PHP worker thread handling the current request. Worker indices range from 0 to PHP_WORKERS - 1.
Returns: An integer identifying the current worker thread.
Example:
<?php
$workerId = oxphp_worker_id();
// Use per-worker temp files to avoid collisions
$tmp = "/tmp/worker_{$workerId}_buffer.dat";
error_log("Worker $workerId handling request");oxphp_server_info()
oxphp_server_info(): arrayReturns an associative array with server and request metadata.
Returns: An array with the following keys:
| Key | Type | Description |
|---|---|---|
version |
string |
Server version (e.g. "0.13.0") |
worker_id |
int |
Same value as oxphp_worker_id() |
request_time |
float |
Unix timestamp with microsecond precision when the request started |
worker_mode |
bool |
Whether the current process runs in worker mode |
runtime_hooks |
array |
The runtime-hook categories this process installed at startup, as a list of category names (["sleep", "streams", "net"]). Empty when RUNTIME_HOOKS enabled nothing; see Runtime hooks |
Example:
<?php
$info = oxphp_server_info();
// [
// "version" => "0.13.0",
// "worker_id" => 3,
// "request_time" => 1738800000.123456,
// "worker_mode" => true,
// "runtime_hooks" => ["sleep", "streams", "net"],
// ]
$elapsed = microtime(true) - $info['request_time'];
echo "Processing took {$elapsed}s so far";oxphp_finish_request()
oxphp_finish_request(): boolFlushes the response to the client and continues PHP execution in the background. The client receives the complete HTTP response immediately; the script keeps running until it exits naturally. This is the OxPHP equivalent of fastcgi_finish_request() in PHP-FPM.
Returns: true on success, false if already called on this request.
The PHP worker thread remains occupied until the script finishes. Keep background work short or offload heavy processing to a queue.
Example:
<?php
http_response_code(202);
echo json_encode(['status' => 'accepted']);
oxphp_finish_request();
// The client already has its 202 response; continue working
send_notification_email($user);
update_analytics($event);oxphp_is_worker()
oxphp_is_worker(): boolReturns whether the server is running in worker mode. Worker mode activates when WORKER_MODE_ENABLED=true.
Returns: true if running in worker mode, false in traditional mode.
Example:
<?php
if (oxphp_is_worker()) {
// Reuse persistent connections across requests
$db = $GLOBALS['db'] ??= new PDO($dsn);
} else {
// Traditional mode: create a new connection per request
$db = new PDO($dsn);
}oxphp_worker()
oxphp_worker(callable $handler): boolEnters the persistent worker mode loop. OxPHP calls $handler once for each incoming HTTP request. Between requests, a soft reset clears per-request state (output buffers, headers, and superglobals) without destroying the PHP heap, so any variables declared outside the handler persist across requests. $_ENV is excluded from that reset on purpose: with PHP's default auto_globals_jit=1 it holds for the life of the worker, so a value the handler writes there is read by every later request on that worker and by any request multiplexed alongside it. See Superglobals.
Parameters:
$handler: Called once per request. The handler receives no arguments. Use superglobals ($_SERVER,$_GET,$_POST, etc.) oroxphp_http_request()inside the handler to access request data.
Returns: true once the loop has ended, false if not in worker mode. Every exit below returns true, so the value does not indicate why the loop ended.
The worker loop exits when any of the following conditions are met:
- The server shuts down gracefully
- 3 consecutive requests come apart. A fatal error in the handler is the usual cause; two failures around the request's fiber also count. An uncaught exception is answered and does not count, and neither does a request the server cancelled unless a fatal was reported on it as well (see Worker mode → Recycling)
- The worker exceeds
WORKER_MAX_MEMORY_MIB - A request was ended with an internal function's call on its stack, and the worker's memory then grew past a threshold. The measurement is taken between two moments with no request in flight and leaves out the fibers the worker created (see Worker mode → Recycling)
- The application calls
Worker::scheduleExit() - A dynamic pool (
PHP_WORKERS=MIN:MAX) retires this worker for being idle longer thanPHP_WORKERS_IDLE_SECONDS(default 30) while holding nothing. The server keeps serving; the pool is scaling down (see Dynamic pool)
Code after oxphp_worker() runs on every one of those exits, not only the first. On a dynamic pool that makes it a recurring event on a healthy server: an idle worker retires, its teardown runs, and the rest of the pool serves on. Write that block as worker teardown rather than server shutdown. A line there that logs "server stopping" is wrong on every exit except the last, and a resource shared beyond the worker is torn down while the server is still running.
oxphp_worker() only works in worker mode (WORKER_MODE_ENABLED=true). In traditional mode it logs a warning and returns false.
Example:
<?php
// worker.php — runs once per worker lifetime
// Bootstrap: executed once on startup
require __DIR__ . '/vendor/autoload.php';
$app = new App();
// Handle requests in a loop
oxphp_worker(function () use ($app) {
$app->handle();
});
// Reached when this worker's loop ends: server shutdown, recycling,
// or a dynamic pool retiring this worker while the server keeps running
$app->terminate();oxphp_is_streaming()
oxphp_is_streaming(): boolReturns whether the current request is in streaming mode. Streaming mode activates on the first call to oxphp_stream_flush() or automatically when PHP sets Content-Type: text/event-stream.
Returns: true if streaming mode is active, false otherwise.
Example:
<?php
if (oxphp_is_streaming()) {
echo "data: " . json_encode($event) . "\n\n";
oxphp_stream_flush();
} else {
echo json_encode($allData);
}oxphp_stream_flush()
oxphp_stream_flush(): boolActivates streaming mode and flushes any buffered output to the client as an HTTP chunk. On the first call, HTTP headers are sent immediately and streaming begins. Each subsequent call flushes output written since the last flush.
Returns: true on success, false if oxphp_finish_request() was already called.
Streaming mode also activates automatically when PHP sets Content-Type: text/event-stream. In that case you can use PHP's built-in flush(), but call ob_end_flush() first to bypass PHP's output buffering layer.
Example:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
for ($i = 0; $i < 10; $i++) {
echo "id: $i\n";
echo "data: " . json_encode(['counter' => $i]) . "\n\n";
oxphp_stream_flush();
oxphp_sleep(1.0); // use oxphp_sleep instead of sleep — cooperative in fiber mode, where a switch is possible
}oxphp_sleep()
oxphp_sleep(float $seconds): voidSleeps for the specified duration. Inside a worker mode handler running in a fiber, this call is cooperative. It suspends the current fiber so other requests can be processed during the wait. Outside a fiber, it falls back to a standard blocking usleep().
It also blocks where a fiber switch is refused, which happens wherever leaving the current frame would be unsafe:
- inside a
declare(ticks)handler - inside pcntl's signal dispatch
- while the request's input is being built
- inside a
FILTER_CALLBACKreached fromfilter_input_array()given a definitions array - inside the teardown walks that run destructors after a request has come apart
It also blocks when called inside a fiber started by a userland scheduler (AMPHP, Revolt, a bare new Fiber), because OxPHP's scheduler cannot resume that context. Runtime hooks describes the same boundary for the hooked native builtins.
oxphp_sleep() cooperates in worker mode with no configuration and accepts fractional seconds. RUNTIME_HOOKS=sleep is a separate, opt-in retrofit that makes native sleep()/usleep() calls (e.g. inside third-party code) cooperate as well, within the same limits. It does not remove the need for oxphp_sleep() in code you control. See Runtime hooks.
Parameters:
$seconds: Duration to sleep in seconds. Fractional values are accepted (e.g.0.5for 500 milliseconds). Values of0or less return immediately.
Returns: void
Example:
<?php
oxphp_worker(function () {
// In worker mode with fiber multiplexing:
// this suspends the fiber rather than blocking the thread
oxphp_sleep(1.0);
echo json_encode(['done' => true]);
});oxphp_usleep()
oxphp_usleep(int $microseconds): voidSleeps for the specified number of microseconds. Like oxphp_sleep(), this is cooperative inside a fiber and falls back to blocking usleep() otherwise, including in the contexts listed under oxphp_sleep() where a fiber switch is refused or OxPHP does not own the context.
Parameters:
$microseconds: Duration to sleep in microseconds. Values of0or less return immediately.
Returns: void
Example:
<?php
oxphp_worker(function () {
// Poll for a condition every 100ms without blocking other requests
while (!$condition_met()) {
oxphp_usleep(100_000);
}
echo "ready";
});oxphp_async()
oxphp_async(Closure $closure, mixed ...$args): intDispatches a closure for execution on a dedicated async worker thread and returns a promise ID immediately. The caller continues executing without waiting for the closure to finish. Use oxphp_async_await() to retrieve the result.
Parameters:
$closure: A user-definedClosureto run on an async worker thread...$args: Arguments to pass to the closure. Scalar values (null,bool,int,float,string), arrays of those, andOxPHP\Shared\*instances (the only objects that may cross the thread boundary) are accepted. Resources and any non-Sharedobject are rejected.
Returns: An integer promise ID. Pass this to oxphp_async_await(), oxphp_async_await_all(), oxphp_async_await_race(), or oxphp_async_await_any().
Throws: OxPHP\Async\AsyncException in the following cases:
- The async pool is disabled (
ASYNC_WORKERS=0), with the message "Async pool is disabled. Set ASYNC_WORKERS > 0 to enable." - The closure is not user-defined
- The async pool is full (all queue slots occupied)
- Arguments or
use-variables contain objects or resources
Use-vars captured via use in the closure follow the same restrictions: objects and resources are rejected.
Example:
<?php
// Dispatch two independent tasks concurrently
$p1 = oxphp_async(function () {
return fetch_from_api('/users');
});
$p2 = oxphp_async(function () {
return fetch_from_api('/posts');
});
// Retrieve both results
$users = oxphp_async_await($p1);
$posts = oxphp_async_await($p2);oxphp_async_await()
oxphp_async_await(int $promise_id, float $timeout = 0.0): mixedBlocks until the specified async promise completes and returns its result. Inside a worker mode fiber, this suspends the current fiber cooperatively rather than blocking the thread.
Parameters:
$promise_id: A promise ID returned byoxphp_async()$timeout: Maximum seconds to wait.0.0means wait indefinitely. Default:0.0
Returns: The return value of the async closure.
Throws:
OxPHP\Async\AsyncExceptionif the async pool is disabled (ASYNC_WORKERS=0), or if the async task threw an exceptionOxPHP\Async\TimeoutExceptionif$timeoutis exceeded
Example:
<?php
$promise = oxphp_async(function (int $n) {
return array_sum(range(1, $n));
}, 1_000_000);
$result = oxphp_async_await($promise);
echo $result; // 500000500000
// With timeout
try {
$result = oxphp_async_await($promise, 5.0);
} catch (\OxPHP\Async\TimeoutException $e) {
echo "Task took too long";
}oxphp_async_await_all()
oxphp_async_await_all(array $promise_ids, float $timeout = 0.0): arrayAwaits all promises in the array and returns an associative array mapping each promise ID to its result. Promises are awaited in array order.
Parameters:
$promise_ids: An array of integer promise IDs returned byoxphp_async()$timeout: Maximum seconds to wait per promise.0.0means wait indefinitely. Default:0.0
Returns: An associative array where each key is a promise ID (integer) and each value is the result of that promise.
Throws:
OxPHP\Async\AsyncExceptionif the async pool is disabled (ASYNC_WORKERS=0), or if any promise failsOxPHP\Async\TimeoutExceptionif any promise exceeds$timeout
Example:
<?php
$promises = [
oxphp_async(fn() => slow_query('users')),
oxphp_async(fn() => slow_query('orders')),
oxphp_async(fn() => slow_query('products')),
];
$results = oxphp_async_await_all($promises);
foreach ($results as $promiseId => $result) {
// process $result
}oxphp_async_await_race()
oxphp_async_await_race(array $promise_ids, float $timeout = 0.0): arrayRaces multiple promises and returns the first one to settle, whether it fulfilled or rejected. The other promises are not cancelled. They continue running and remain awaitable with oxphp_async_await(). This is the JavaScript Promise.race analog.
Parameters:
$promise_ids: An array of at least one integer promise ID returned byoxphp_async(). Must not be empty.$timeout: Maximum seconds to wait for any promise to settle.0.0means wait indefinitely. Default:0.0
Returns: An associative array with two keys:
id(int): The promise ID of the winnervalue(mixed): The return value of the winning promise
Throws:
OxPHP\Async\AsyncExceptionif the async pool is disabled (ASYNC_WORKERS=0), or if the winning promise rejectedOxPHP\Async\TimeoutExceptionif no promise settles within$timeout
Example:
<?php
// Try two mirror endpoints; use whichever responds first
$p1 = oxphp_async(fn() => fetch('https://mirror-1.example.com/data'));
$p2 = oxphp_async(fn() => fetch('https://mirror-2.example.com/data'));
$winner = oxphp_async_await_race([$p1, $p2], timeout: 10.0);
echo "Mirror {$winner['id']} won: " . json_encode($winner['value']);oxphp_async_await_any()
oxphp_async_await_any(array $promise_ids, float $timeout = 0.0): arrayReturns as soon as one promise FULFILLS. Rejections are accumulated, and only become observable if every promise rejects. This is the JavaScript Promise.any analog, useful for fallback / redundancy patterns where you want any source that works.
Parameters:
$promise_ids: An array of at least one integer promise ID returned byoxphp_async(). Must not be empty.$timeout: Maximum seconds to wait for the first fulfillment.0.0means wait indefinitely. Default:0.0
Returns: An associative array with two keys:
id(int): The promise ID of the first fulfilled promisevalue(mixed): The return value of the winning promise
Throws:
OxPHP\Async\AsyncExceptionif the async pool is disabled (ASYNC_WORKERS=0)OxPHP\Async\AggregateAsyncExceptionif every promise rejected. The exception carries every error viagetErrors()(positional, keyed 0..N-1),getErrorMap()(id-keyed), andgetPromiseIds().OxPHP\Async\TimeoutExceptionif no promise fulfilled within$timeout.getPartialErrors()lists the promises that already rejected before the deadline;getCancelledPromiseIds()lists those that had not settled and have therefore been cancelled. Their cancel flag is set and their receivers are dropped, so passing any of these ids tooxphp_async_await*()afterwards throws"unknown or already-awaited promise id". The list is an audit trail, not a queue of resumable work.
Behavior:
- Promises that were still pending at the moment of victory remain awaitable individually with
oxphp_async_await(). - Promises that already rejected before the winner do not, because their results were consumed when accumulated as candidate errors.
Example:
<?php
$mirror_a = oxphp_async(fn() => fetch('https://mirror-a.example.com/data'));
$mirror_b = oxphp_async(fn() => fetch('https://mirror-b.example.com/data'));
$mirror_c = oxphp_async(fn() => fetch('https://mirror-c.example.com/data'));
try {
$winner = oxphp_async_await_any([$mirror_a, $mirror_b, $mirror_c], 5.0);
echo "Mirror {$winner['id']} responded: " . json_encode($winner['value']);
} catch (\OxPHP\Async\AggregateAsyncException $e) {
// every mirror rejected
foreach ($e->getErrorMap() as $promise_id => $err) {
error_log("mirror {$promise_id}: " . $err->getMessage());
}
} catch (\OxPHP\Async\TimeoutException $e) {
// deadline elapsed before any mirror fulfilled
$partial = $e->getPartialErrors();
$cancelled = $e->getCancelledPromiseIds();
}oxphp_register_decorator()
oxphp_register_decorator(string $class): boolRegisters a PHP class as a decorator that wraps function and method calls. The class must implement OxPHP\Decorator\AttributeInterface. Once registered, OxPHP invokes the decorator's before() and after() hooks around every function or method call that matches the decorator's #[Attribute] targets.
Parameters:
$class: The fully qualified class name of the decorator to register
Returns: true on success, false if the class does not exist or does not implement OxPHP\Decorator\AttributeInterface.
Example:
<?php
use OxPHP\Decorator\AttributeInterface;
use OxPHP\Decorator\Context;
#[\Attribute(\Attribute::TARGET_METHOD)]
class LogDecorator implements AttributeInterface
{
public function before(Context $ctx): void
{
error_log("Calling {$ctx->target} (request {$ctx->requestId})");
}
public function after(Context $ctx): void
{
error_log("Finished {$ctx->target}");
}
}
// Register once at bootstrap (or worker startup)
oxphp_register_decorator(LogDecorator::class);oxphp_apm_trace()
oxphp_apm_trace(string $name, callable $callback, ?array $attributes = null): mixedExecutes a callback inside a named span. The span is opened before the callback runs and closed after it returns or throws, so no oxphp_apm_end() call is needed.
The callback receives the span's local ID as its only argument. Pass it to oxphp_apm_attribute(), oxphp_apm_event() or oxphp_apm_status() to target this span explicitly rather than whichever span is current.
If the callback throws, the span is marked as error and an exception event carrying exception.type and exception.message is recorded on it, then the exception propagates unchanged. No exception.stacktrace is recorded: collecting one costs a re-entry into PHP on every throw, which this path takes far more often than oxphp_apm_error() does. Call oxphp_apm_error($e) explicitly if you need the stacktrace.
When APM is disabled (OTEL_APM_ENABLED unset or falsy; it accepts on, true, 1 and yes, case-insensitively), the callback still runs and its return value is still forwarded. It receives 0 as the span ID and nothing is recorded.
A $callback that is not callable raises a TypeError. If resolving it raises an exception of its own instead (an autoloader that throws on a 'Class::method' string, or an error handler that throws on the deprecation notice for a 'self::method' string), that exception propagates as it is and the callback does not run.
Parameters:
$name: Span name$callback: Callable to execute inside the span; receivesint $span_id$attributes: Optional associative array of string key-value attributes
Returns: mixed, the value $callback returned.
Example:
<?php
$user = oxphp_apm_trace('user.fetch', function (int $span) use ($id) {
oxphp_apm_attribute('user.id', (string) $id, $span);
return User::find($id);
}, ['db.system' => 'mysql']);oxphp_apm_start()
oxphp_apm_start(string $name, ?array $attributes = null): intOpens a new span and returns a local ID for later reference. The span becomes a child of the currently active span (or the request root span if no span is active). Use oxphp_apm_end() to close it.
Parameters:
$name: Span name (e.g."cache.warm","payment.process")$attributes: Optional associative array of string key-value attributes to set on the span at creation
Returns: An integer local span ID. Pass this to oxphp_apm_end(), oxphp_apm_attribute(), or other functions that accept a $span_id. Returns 0 when APM is disabled.
Example:
<?php
$spanId = oxphp_apm_start('order.validate', [
'order.type' => 'subscription',
]);
validateOrder($order);
oxphp_apm_end($spanId);oxphp_apm_end()
oxphp_apm_end(int $span_id): voidCloses the span opened by oxphp_apm_start(). The span's end time is recorded and it moves from the active stack to the finished list, ready for export.
Parameters:
$span_id: The local span ID returned byoxphp_apm_start()
Returns: void
Always close spans in reverse order. If you open span A then span B, close B before A. Unclosed spans are automatically closed at request end and marked with oxphp.span.leaked=true.
oxphp_apm_attribute()
oxphp_apm_attribute(string $key, mixed $value, ?int $span_id = null): voidSets a key-value attribute on a span. Values are converted to strings. If no $span_id is provided, the attribute is added to the currently active span.
Parameters:
$key: Attribute key (e.g."user.id","cache.hit")$value: Attribute value (string, int, float, bool, or null; converted to string)$span_id: Optional local span ID. When omitted, targets the current span
Returns: void
Example:
<?php
$spanId = oxphp_apm_start('db.query');
oxphp_apm_attribute('db.system', 'mysql');
oxphp_apm_attribute('db.statement', 'SELECT * FROM users WHERE id = ?');
oxphp_apm_attribute('db.row_count', $rowCount, $spanId);
oxphp_apm_end($spanId);oxphp_apm_event()
oxphp_apm_event(string $name, ?array $attributes = null, ?int $span_id = null): voidRecords a timestamped event on a span. Events are useful for logging discrete occurrences within a span's lifetime (e.g. cache miss, retry attempt, authorization check).
Parameters:
$name: Event name (e.g."cache.miss","retry")$attributes: Optional associative array of string key-value event attributes$span_id: Optional local span ID. When omitted, targets the current span
Returns: void
Example:
<?php
$spanId = oxphp_apm_start('payment.process');
oxphp_apm_event('payment.authorized', [
'provider' => 'stripe',
'amount' => '49.99',
]);
oxphp_apm_end($spanId);oxphp_apm_error()
oxphp_apm_error(mixed $exception, ?int $span_id = null): voidMarks a span's status as error (status code 2). Use this to flag spans where an exception or failure occurred.
Parameters:
$exception: The exception or error (used for context; status is set regardless of type)$span_id: Optional local span ID. When omitted, targets the current span
Returns: void
Example:
<?php
$spanId = oxphp_apm_start('external.api');
try {
$result = callExternalApi();
} catch (\Throwable $e) {
oxphp_apm_error($e, $spanId);
throw $e;
} finally {
oxphp_apm_end($spanId);
}oxphp_apm_status()
oxphp_apm_status(int $code, ?string $description = null, ?int $span_id = null): voidSets the status code and optional description on a span.
Parameters:
$code: Status code (0= Unset,1= Ok,2= Error)$description: Optional human-readable status description$span_id: Optional local span ID. When omitted, targets the current span
Returns: void
Example:
<?php
$spanId = oxphp_apm_start('validation');
if ($valid) {
oxphp_apm_status(1, 'Validation passed', $spanId);
} else {
oxphp_apm_status(2, 'Invalid input: missing email', $spanId);
}
oxphp_apm_end($spanId);oxphp_apm_trace_id()
oxphp_apm_trace_id(): stringReturns the W3C trace ID (32 hex characters) for the current request's trace context. This is the same value as $_SERVER['OXPHP_TRACE_ID'], available without superglobals.
Returns: A 32-character hexadecimal trace ID string. Returns an empty string when APM is disabled or no trace context is active.
Example:
<?php
$traceId = oxphp_apm_trace_id();
error_log("Processing request in trace {$traceId}");oxphp_apm_span_id()
oxphp_apm_span_id(): stringReturns the span ID (16 hex characters) of the currently active span. If there are nested spans, this returns the innermost open span's ID.
Returns: A 16-character hexadecimal span ID string. Returns an empty string when no span is active.
oxphp_apm_header()
oxphp_apm_header(): stringReturns a W3C traceparent header value for the current span context. Use this to propagate trace context to downstream HTTP calls.
Returns: A string in the format 00-{trace_id}-{span_id}-01. Returns an empty string when no trace context is active.
Example:
<?php
$spanId = oxphp_apm_start('http.call');
$traceparent = oxphp_apm_header();
$response = file_get_contents('https://api.example.com/data', false,
stream_context_create([
'http' => [
'header' => "traceparent: {$traceparent}\r\n",
],
])
);
oxphp_apm_end($spanId);OxPHP\Profile\is_active()
OxPHP\Profile\is_active(): boolReturns true when profile capture is currently active for this request. That means a trigger turned it on (header, cookie, query parameter, or sample rate) or the script called start(), and capture is not paused by pause() or stop(). With the profiler disabled (PROFILER_ENABLED), triggers are not checked and start() does nothing, so neither turns capture on.
Use it to guard expensive instrumentation that should only run when profiling is on.
Returns: bool.
Example:
<?php
if (OxPHP\Profile\is_active()) {
OxPHP\Profile\mark('checkpoint.before_query');
}OxPHP\Profile\start()
OxPHP\Profile\start(): voidProgrammatically enables profile capture for the remainder of the current request, even if no trigger fired at RINIT. Sets profiling mode to PROFILE_ALL and clears the paused flag.
Does nothing while the profiler is disabled (PROFILER_ENABLED): the profiler records no function calls then, so the call neither turns capture on nor makes is_active() return true.
If a profile was already active in a different mode, this call promotes it and discards any spans already collected in the lower mode, so the captured profile is internally consistent. Use this when you want to opt a specific code path into profiling without relying on triggers.
Returns: void.
Example:
<?php
if ($request->header('x-debug') === 'on') {
OxPHP\Profile\start();
}OxPHP\Profile\stop()
OxPHP\Profile\stop(): voidDisables further span capture for this request. Currently open spans close naturally as PHP returns from them, so the call stack remains balanced. Only new spans stop being recorded.
Returns: void.
Example:
<?php
OxPHP\Profile\start();
expensive_work();
OxPHP\Profile\stop();
non_profiled_work();OxPHP\Profile\pause()
OxPHP\Profile\pause(): voidSoft variant of stop(), with the same effect (it sets the paused flag). The two differ only in intent: pause() signals that capture will resume later via resume(), while stop() does not.
Returns: void.
OxPHP\Profile\resume()
OxPHP\Profile\resume(): voidClears the paused flag set by pause() or stop(). Profile mode itself is not changed, so if it was never enabled, resume() does nothing observable.
Returns: void.
Example:
<?php
OxPHP\Profile\pause();
$secret = decrypt_payload($data);
OxPHP\Profile\resume();OxPHP\Profile\mark()
OxPHP\Profile\mark(string $label, ?array $attrs = null): voidAttaches a Mark event to the topmost open span, with an optional attribute bag. No-op when no span is open (e.g. profiling not active, or mark() called at request top-level outside any instrumented frame).
Attribute keys and values are coerced to strings; non-string values become an empty string.
Parameters:
$label: short human-readable name for the event (e.g."cache.miss","db.slow_query")$attrs: optionalarray<string, scalar>of key/value pairs attached to the event
Returns: void.
Example:
<?php
function load_user(int $id): array {
$cached = $cache->get("user:$id");
if ($cached === null) {
OxPHP\Profile\mark('cache.miss', ['key' => "user:$id"]);
$cached = $db->fetchUser($id);
}
return $cached;
}OxPHP\Profile\metric()
OxPHP\Profile\metric(string $name, float $value): voidAppends a metric.<name> attribute to the current open span. No-op when no span is open.
Unlike mark() (which creates a discrete event), metric() writes onto the existing span's attribute set. It is meant for numeric observations tied to the surrounding operation (rows fetched, bytes processed, retry count).
Parameters:
$name: metric identifier; will be stored asmetric.<name>$value: numeric value (coerced tofloat)
Returns: void.
Example:
<?php
function search(string $query): array {
$results = $index->search($query);
OxPHP\Profile\metric('result_count', count($results));
return $results;
}Classes and Interfaces
The oxphp_sapi extension registers the following classes:
HTTP
| Class | Description |
|---|---|
OxPHP\Http\Request |
Request object returned by oxphp_http_request(). final, so it cannot be extended. |
OxPHP\Http\Attributes |
Mutable request attributes container (for middleware). final. |
OxPHP\Http\Session |
Session object accessible via $request->session(). final. |
OxPHP\Http\UploadedFile |
Uploaded file object from $request->files(). final. |
Decorators
| Class / Interface | Description |
|---|---|
OxPHP\Decorator\AttributeInterface |
Interface for decorators. Requires before(Context $ctx) and after(Context $ctx) methods. |
OxPHP\Decorator\Context |
Context object passed to decorator hooks. final. Public properties: target, class, method, function, objectId, requestId, traceId. Methods: getParams(): array, getResult(): mixed, hasResult(): bool. See Decorators for the full reference. |
Tracing
| Class | Description |
|---|---|
OxPHP\Apm\Trace |
Built-in attribute for automatic span creation. Apply to functions or methods. |
Async
| Class | Description |
|---|---|
OxPHP\Async\BorrowedProxy |
Proxy object for borrowed values between threads. |
Exceptions
All exceptions registered by the extension:
| Exception | Extends | When thrown |
|---|---|---|
OxPHP\Async\AsyncException |
\Exception |
Error in an async task (oxphp_async_await()) or invalid arguments in oxphp_async() |
OxPHP\Async\TimeoutException |
OxPHP\Async\AsyncException |
Timeout exceeded in any of oxphp_async_await(), oxphp_async_await_all(), oxphp_async_await_race(), or oxphp_async_await_any(). For oxphp_async_await_any() timeouts the accessors getPartialErrors(): array<int, \Throwable> and getCancelledPromiseIds(): list<int> are populated; for the other call sites both return []. |
OxPHP\Async\AggregateAsyncException |
OxPHP\Async\AsyncException |
Thrown by oxphp_async_await_any() when every promise rejected. Methods: getErrors(): list<\Throwable> (positional, keyed 0..N-1 by input position), getErrorMap(): array<int, \Throwable> (keyed by promise id), getPromiseIds(): list<int> (input promise ids in order). |
OxPHP\Async\BorrowException |
\Exception |
Error borrowing a value between threads |
OxPHP\Http\Exception\NoActiveRequestException |
\RuntimeException |
Calling oxphp_http_request() outside an active request |
OxPHP\Http\Exception\AsyncContextException |
NoActiveRequestException |
Calling oxphp_http_request() inside an oxphp_async() callback |
OxPHP\Http\Exception\WorkerIdleException |
NoActiveRequestException |
Calling oxphp_http_request() in worker mode between requests |
OxPHP\Decorator\RejectedException |
\Exception |
A decorator rejected a function/method call |
Extension Verification
You can verify that the OxPHP extension is loaded and inspect all registered functions:
<?php
if (extension_loaded('oxphp_sapi')) {
echo "OxPHP extension is loaded\n";
}
$functions = get_extension_funcs('oxphp_sapi');
print_r($functions);
// Array
// (
// [0] => oxphp_http_request
// [1] => oxphp_superglobals_enabled
// [2] => oxphp_request_id
// [3] => oxphp_worker_id
// [4] => oxphp_server_info
// [5] => oxphp_finish_request
// [6] => oxphp_is_worker
// [7] => oxphp_is_streaming
// [8] => oxphp_stream_flush
// [9] => oxphp_sleep
// [10] => oxphp_usleep
// [11] => oxphp_worker
// [12] => oxphp_register_decorator
// [13] => oxphp_async
// [14] => oxphp_async_await
// [15] => oxphp_async_await_all
// [16] => oxphp_async_await_race
// [17] => oxphp_async_await_any
// [18] => oxphp_apm_trace
// [19] => oxphp_apm_start
// [20] => oxphp_apm_end
// [21] => oxphp_apm_attribute
// [22] => oxphp_apm_event
// [23] => oxphp_apm_error
// [24] => oxphp_apm_status
// [25] => oxphp_apm_trace_id
// [26] => oxphp_apm_span_id
// [27] => oxphp_apm_header
// )The core SAPI functions (through oxphp_register_decorator) come first. The oxphp_async_* and oxphp_apm_* families are appended by their plugins during module init, as is the OxPHP\Profile\* SDK when the profiler is built in. Treat this list as illustrative: the exact set and ordering depend on which plugins are compiled into the build.
Compatibility with PHP-FPM
If your code must run on both OxPHP and PHP-FPM, use fallback wrappers:
<?php
function finish_request(): bool
{
if (function_exists('oxphp_finish_request')) {
return oxphp_finish_request();
}
if (function_exists('fastcgi_finish_request')) {
return fastcgi_finish_request();
}
return false;
}
// Worker-aware bootstrap
if (function_exists('oxphp_is_worker') && oxphp_is_worker()) {
// OxPHP worker mode
} else {
// PHP-FPM or OxPHP traditional mode
}The oxphp_async() family of functions is always registered in OxPHP, so function_exists('oxphp_async') returns true even when ASYNC_WORKERS=0. When the pool is disabled, calling any async function throws OxPHP\Async\AsyncException. If your code must handle both configurations, catch the exception rather than checking function_exists().
See Also
- HTTP Request API: object-oriented access to request data via
oxphp_http_request() - Worker Mode: persistent worker loop and request lifecycle
- Server-Sent Events: real-time streaming with
oxphp_stream_flush() - Early Response: background processing with
oxphp_finish_request() - Superglobals: how OxPHP populates
$_SERVER,$_GET,$_POST, and other superglobals - Distributed Tracing & APM: W3C Trace Context, OTel export, and the
oxphp_apm_*()SDK - Configuration Reference:
WORKER_MODE_ENABLED,ENTRY_FILE,PHP_WORKERS, and other env vars