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

# PHP SDK - Caching Behaviour

> When the PHP SDK hits the network, ETag/304 protocol, FPM per-request model, and file-type secret cleanup.

The SDK keeps every config, secret, and flag in an **in-memory array** that is replaced atomically on each sync. Read methods never block the network after the first sync.

## When does the SDK hit the network

| Event                                   | Result                                                                                              |
| --------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `NexusClient::create(...)`              | **Blocking** full sync. The method only returns once the cache is populated (or with an exception). |
| Read within the TTL window              | **No network** - served from the in-memory array                                                    |
| Read after TTL has elapsed              | Stale cache returned **immediately**; a one-shot background sync is triggered                       |
| Server returns `304 Not Modified`       | TTL reset, no payload downloaded                                                                    |
| SSE event received (in `connectStream`) | Immediate full sync, regardless of TTL                                                              |
| `sync()` called directly                | Immediate full sync, regardless of TTL                                                              |

## PHP-FPM per-request model

<Note>
  In PHP-FPM, each HTTP request boots a fresh PHP process. The in-memory cache is empty at the start of every request and `NexusClient::create` makes a **blocking network call** on every request. The TTL has no effect across requests because there is no shared memory.

  **Recommendation:** register the client in a DI container with request scope (the default in Symfony/Laravel) so the cache is built once per request and all service classes share it within that request.
</Note>

For long-running processes (CLI daemons, Swoole workers, ReactPHP servers), the cache persists across multiple reads as expected and the TTL controls background refresh frequency.

## ETag / 304 protocol

Every successful sync stores the server's `ETag` header. The next sync sends `If-None-Match: <etag>`. If nothing has changed:

```
HTTP/1.1 304 Not Modified
```

The SDK resets its internal TTL timestamp and serves the existing cache. No payload is transferred. When the ETag changes, the new payload replaces the cache atomically.

## Background sync errors

Background TTL syncs **never throw exceptions** to the caller. If the network call fails:

1. The error is logged via the configured PSR-3 logger at the `error` level.
2. The existing cache continues to be served.
3. The next read after the TTL elapses retries automatically.

The application is never notified - a transient backend blip does not surface as a customer-facing error.

## Error-never-clears-cache rule

The SDK never empties the cache on a sync failure. Even a `NexusBillingException` or `NexusRateLimitedException` leaves the last-known-good snapshot intact. This is intentional: it is safer to serve potentially stale config values than to serve nothing.

## Cache lifetime

The cache lives for the lifetime of the `NexusClient` object.

* **FPM/HTTP workers** - cache lives for one request (PHP process is recycled after the response).
* **CLI daemons / queue workers** - cache lives for the lifetime of the process.

Share the client instance rather than constructing one per call:

```php theme={null}
// Good - one client per request (DI container handles sharing)
$container->singleton(NexusClient::class, fn() => NexusClient::create($config));

// Bad - new client (and blocking sync) on every call
function getFlag(string $key): bool {
    return NexusClient::create($config)->getFlag($key); // don't do this
}
```

## File-type secrets

Secrets with `type = 'file'` are materialised to a temp file on every sync:

```
sys_get_temp_dir() . '/nexus-<sanitised-key>-<sha256-prefix>'
```

The hash prefix is derived from the secret value, so the path changes whenever the value changes and the old file is removed automatically. Files are written with mode `0600`.

Temp files are deleted when the `NexusClient` object is garbage-collected (`__destruct`). In FPM, this happens at the end of each request.

```php theme={null}
// The path is stable for the lifetime of a given secret value
$certPath = $client->getSecretFilePath('TLS_CERT');
// Pass to openssl, curl CURLOPT_SSLCERT, file_get_contents, etc.
```

## Diagnostics

Enable debug-level logging in your PSR-3 logger to see one line per sync:

```
[nexus] Sync OK. configs=12 secrets=4 flags=7 key_type=sk
[nexus] Sync 304 Not Modified - cache retained.
```

See [Custom logging](custom-logging) for how to wire up a logger.
