# 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.

## The middleware interface

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

```php title="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.

```php
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");
        }
    }
}
```

## Registering middleware

Add your middleware through the `middleware` option on [`DriverOptions`](/docs/reference/php/v2/api/utilities.md#driveroptions). The list is appended to the pipeline in order.

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

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

## Pipeline order

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.

## Built-in middleware

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

### Logging

`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.

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

### Telemetry

`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.

```php
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](/docs/reference/php/v2/concepts/observability.md) for what the telemetry step records.

### Retry

`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.

```php title="Constructor"
new RetryMiddleware(
    int $maxAttempts = 3,
    float $baseDelaySeconds = 0.1,
    Scheduler $scheduler = new SyncScheduler(),
)
```

```php
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](/docs/reference/php/v2/concepts/runtimes.md), pass the matching scheduler so the backoff delay does not block other tasks.

### Authentication

`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()`](/docs/reference/php/v2/concepts/connecting-to-surrealdb.md#authentication-details), so you do not register it manually.

## Learn more

- [Observability](/docs/reference/php/v2/concepts/observability.md) for the telemetry step in depth
- [Events](/docs/reference/php/v2/concepts/events.md) for observing requests without writing middleware
- [Runtimes and workers](/docs/reference/php/v2/concepts/runtimes.md) for the schedulers retry backoff uses
- [Utilities](/docs/reference/php/v2/api/utilities.md#driveroptions) for the `DriverOptions` fields
