• Start

Languages

/

PHP

/

v2 (alpha)

/

Concepts

Middleware

Intercept every RPC in version 2 of the PHP SDK with middleware, including the built-in logging, telemetry, retry, and authentication steps.

Every call the SDK makes to SurrealDB passes through a middleware pipeline before it reaches the transport. Middleware is the SDK's primary extension seam: authentication, logging, telemetry, and retries all participate as middleware, and you can add your own.

The design is inspired by PSR-15, adapted to the SurrealDB RPC request and response. Each middleware receives the request and a $next callable, and returns a response. It can inspect or modify the request, short-circuit the call, retry it, or observe the result.

A middleware implements SurrealDB\SDK\Contracts\MiddlewareInterface, which has a single method.

Syntax

public function process(RpcRequest $request, callable $next): RpcResponse

Call $next($request) to pass control to the next step in the pipeline. The innermost step is the engine's send() primitive, which performs the actual RPC.

use SurrealDB\SDK\Contracts\MiddlewareInterface;
use SurrealDB\SDK\Rpc\RpcRequest;
use SurrealDB\SDK\Rpc\RpcResponse;

final class TimingMiddleware implements MiddlewareInterface
{
public function process(RpcRequest $request, callable $next): RpcResponse
{
$start = hrtime(true);

try {
return $next($request);
} finally {
$elapsed = (hrtime(true) - $start) / 1_000_000;
error_log("{$request->method} took {$elapsed} ms");
}
}
}

Add your middleware through the middleware option on DriverOptions. The list is appended to the pipeline in order.

use SurrealDB\SDK\Surreal;
use SurrealDB\SDK\Connection\DriverOptions;

$db = new Surreal(new DriverOptions(
middleware: [
new TimingMiddleware(),
],
));

The pipeline is assembled when the engine connects. The first middleware in the pipeline runs outermost, so it sees the request first and the response last. The SDK builds the pipeline in this order:

  1. Logging, when a PSR-3 logger is configured.

  2. Telemetry, when a tracer or meter is configured.

  3. Your middleware, in the order given to the middleware option.

The built-in steps are added only when their dependency is configured, so a default new Surreal() runs with an empty pipeline.

The following middleware ships with the SDK in the SurrealDB\SDK\Middleware namespace.

LoggingMiddleware logs every request, its outcome, and its timing to a PSR-3 logger. It is a pure observation step and never alters the request or response. The SDK adds it automatically when you set the logger option, so you rarely construct it by hand.

$db = new Surreal(new DriverOptions(logger: $logger));

TelemetryMiddleware emits a trace span and duration and count metrics for every RPC. The SDK adds it automatically when you set a tracer or meter. It accepts a recordQueryText flag (off by default) to include SurrealQL text on query spans.

use SurrealDB\SDK\Middleware\TelemetryMiddleware;

// Register manually only when you need to capture query text.
$db = new Surreal(new DriverOptions(
middleware: [
new TelemetryMiddleware($tracer, $meter, recordQueryText: true),
],
));

See Observability for what the telemetry step records.

RetryMiddleware retries calls that fail with a transient connection error, using exponential backoff. It is opt-in rather than part of the default pipeline, because blindly retrying is unsafe for operations that are not idempotent.

Constructor

new RetryMiddleware(
int $maxAttempts = 3,
float $baseDelaySeconds = 0.1,
Scheduler $scheduler = new SyncScheduler(),
)
use SurrealDB\SDK\Middleware\RetryMiddleware;

$db = new Surreal(new DriverOptions(
middleware: [
new RetryMiddleware(maxAttempts: 5),
],
));

It retries HttpConnectionException, CallTerminatedException, and ConnectionUnavailableException. On an async runtime, pass the matching scheduler so the backoff delay does not block other tasks.

AuthMiddleware re-authenticates and retries a call once when it fails with an authentication error, such as an expired token. The SDK wires this in itself when you provide credentials on connect(), so you do not register it manually.

Was this page helpful?