> ## Documentation Index
> Fetch the complete documentation index at: https://docs.auditrails.io/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP SDK — AuditRails Composer Audit Logging Client

> Integrate AuditRails into any PHP application. PSR-18 compatible, Laravel-ready, automatic batching, and PSR-3 logger support out of the box.

The AuditRails PHP SDK is a PSR-18 compatible client library for PHP 8.1 and later. Rather than bundling an HTTP client, it accepts any PSR-18 compatible implementation — Guzzle, Symfony HttpClient, or any other library you already have in your project. Events are buffered in memory and flushed in the background via `register_shutdown_function`, so your application code is never blocked by network I/O and no events are lost when the request ends.

## Installation

```bash theme={null}
composer require auditrails/auditrails-php
```

You also need a PSR-18 HTTP client and PSR-7 HTTP factories. If you are not already using one, Guzzle is a common choice:

```bash theme={null}
composer require guzzlehttp/guzzle guzzlehttp/psr7
```

Requires **PHP 8.1+**.

## Initialization

Construct an `AuditRails` instance by passing a `Config` object and your PSR-18 HTTP client along with its request and stream factories.

```php theme={null}
use AuditRails\AuditRails;
use AuditRails\Config;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

$audit = new AuditRails(
    config: new Config(apiKey: $_ENV['AUDITRAILS_API_KEY']),
    httpClient: new Client(),
    requestFactory: new HttpFactory(),
    streamFactory: new HttpFactory(),
);
```

Create one instance per application lifecycle (e.g. in a service container) and reuse it throughout. The `autoShutdown` option (enabled by default) registers a `register_shutdown_function` that automatically flushes buffered events at the end of every PHP request or script.

### Configuration options

| Option          | Type              | Default                     | Description                                             |
| --------------- | ----------------- | --------------------------- | ------------------------------------------------------- |
| `apiKey`        | `string`          | *(required)*                | Your AuditRails API key.                                |
| `baseUrl`       | `string`          | `https://api.auditrails.io` | Override the API endpoint.                              |
| `batchSize`     | `int`             | `100`                       | Maximum events per HTTP request.                        |
| `flushInterval` | `float`           | `1.0` (seconds)             | Buffer flush interval.                                  |
| `maxRetries`    | `int`             | `3`                         | Retry attempts on `5xx` responses.                      |
| `timeout`       | `float`           | `10.0` (seconds)            | Per-request timeout in seconds.                         |
| `autoShutdown`  | `bool`            | `true`                      | Register `register_shutdown_function` to flush on exit. |
| `logger`        | `LoggerInterface` | `null`                      | Any PSR-3 compatible logger (e.g. Monolog).             |

## Logging events

### Buffered logging (recommended)

`$audit->log()` adds an `AuditEvent` to the in-memory buffer and returns immediately. The buffer is automatically flushed at the end of the request. This method **never throws**.

```php theme={null}
use AuditRails\AuditEvent;

$audit->log(new AuditEvent(
    action: 'user.login',
    actorId: 'user_123',
    resource: 'session/sess_abc',
    metadata: ['ip' => '203.0.113.1', 'method' => 'oauth2'],
));
```

### Direct (immediate) logging

`$audit->logDirect()` sends the event immediately and returns the API response. It **throws `AuditRailsError`** on failure.

```php theme={null}
$response = $audit->logDirect(new AuditEvent(
    action: 'document.deleted',
    actorId: 'user_456',
    resource: 'document/doc-789',
));

echo $response->logId;      // "01HXYZ..."
echo $response->requestId;  // AuditRails request ID for support
```

### Direct batch logging

Send multiple events in a single HTTP request, bypassing the buffer.

```php theme={null}
$audit->logBatchDirect([
    new AuditEvent(action: 'document.created', actorId: 'user_123', resource: 'document/doc-001'),
    new AuditEvent(action: 'document.shared',  actorId: 'user_123', resource: 'document/doc-001'),
]);
```

### Manual flush

```php theme={null}
$audit->flush();
```

## Framework integration

### Laravel

**Service provider registration**

Bind `AuditRails` as a singleton in your `AppServiceProvider` (or a dedicated `AuditServiceProvider`). Laravel resolves Guzzle via its built-in HTTP client, so you can pull the factory from the container.

```php theme={null}
use AuditRails\AuditRails;
use AuditRails\Config;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Illuminate\Support\ServiceProvider;

class AuditServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(AuditRails::class, function () {
            return new AuditRails(
                config: new Config(apiKey: config('services.auditrails.api_key')),
                httpClient: new Client(),
                requestFactory: new HttpFactory(),
                streamFactory: new HttpFactory(),
            );
        });
    }
}
```

```php theme={null}
// config/services.php
'auditrails' => [
    'api_key' => env('AUDITRAILS_API_KEY'),
],
```

**Middleware**

Create a reusable middleware that accepts the audit action as a route-level parameter:

```php theme={null}
use AuditRails\AuditEvent;
use AuditRails\AuditRails;
use Closure;
use Illuminate\Http\Request;

class AuditLog
{
    public function __construct(private readonly AuditRails $audit) {}

    public function handle(Request $request, Closure $next, string $action): mixed
    {
        $response = $next($request);

        $this->audit->log(new AuditEvent(
            action: $action,
            actorId: $request->user()?->id,
            resource: $request->method() . ' ' . $request->path(),
            metadata: [
                'ip'     => $request->ip(),
                'status' => $response->getStatusCode(),
            ],
        ));

        return $response;
    }
}
```

Register the middleware alias in `bootstrap/app.php` (Laravel 11+) or `Kernel.php` (Laravel 10), then apply it to routes:

```php theme={null}
// routes/web.php
Route::post('/documents', [DocumentController::class, 'store'])
    ->middleware('audit:document.created');

Route::delete('/documents/{id}', [DocumentController::class, 'destroy'])
    ->middleware('audit:document.deleted');
```

## Error handling

`log()` never throws. For `logDirect()` and `logBatchDirect()`, catch `AuditRailsError` to inspect the failure details.

```php theme={null}
use AuditRails\AuditRailsError;

try {
    $audit->logDirect(new AuditEvent(action: 'user.login', actorId: 'user_123'));
} catch (AuditRailsError $e) {
    echo $e->statusCode;                                        // HTTP status, e.g. 401
    echo $e->errorCode;                                         // Machine-readable code, e.g. "INVALID_API_KEY"
    echo $e->isRetryable() ? 'retryable' : 'not retryable';    // Retry hint
}
```

### PSR-3 logging

Pass any PSR-3 compatible logger as the `logger` argument to receive internal SDK diagnostics (retry attempts, flush failures, etc.):

```php theme={null}
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('auditrails');
$logger->pushHandler(new StreamHandler('php://stderr'));

$audit = new AuditRails(
    config: new Config(apiKey: $_ENV['AUDITRAILS_API_KEY']),
    httpClient: new Client(),
    requestFactory: new HttpFactory(),
    streamFactory: new HttpFactory(),
    logger: $logger,
);
```

## Graceful shutdown

When `autoShutdown` is `true` (the default), the SDK calls `register_shutdown_function` and flushes all buffered events automatically at the end of every PHP request or CLI script. No additional configuration is required in most applications.

To flush manually — for example after a large import job or before a planned maintenance window — call:

```php theme={null}
$audit->flush();
```

To flush and explicitly tear down the client (useful in long-running CLI commands or queue workers):

```php theme={null}
$audit->close();
```

<Note>
  In long-running PHP processes such as queue workers (Laravel Horizon, ReactPHP, Swoole), each job runs in the same PHP process. `register_shutdown_function` fires only when the process itself exits, not between jobs. Call `$audit->flush()` at the end of each job to ensure events from that job are delivered promptly.
</Note>
