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

# PHP SDK - HTTP Client

> PSR-18 and PSR-17 support, discovery, injecting your own client, status-code handling, and why live updates use ext-curl.

The PHP SDK talks to Nexus through a **PSR-18** `ClientInterface` and **PSR-17** factories. It depends on the interfaces rather than on a concrete client, so it uses whichever HTTP stack your project already has - and does not install a second one alongside it.

## Discovery

Pass nothing and [`php-http/discovery`](https://docs.php-http.org/en/latest/discovery.html) finds the implementation your project has installed:

```php theme={null}
$client = NexusClient::create(new NexusConfig(
    baseUrl: 'https://blue-ocean-a5rx7.westyx.dev',
    apiKey:  getenv('NEXUS_API_KEY'),
));
```

If there is none, `create()` raises an error naming what to install.

## Injecting your own

Pass your configured client to reuse its proxy settings, middleware and instrumentation for Nexus traffic too:

```php theme={null}
$client = NexusClient::create(
    $config,
    $myPsr18Client,     // Psr\Http\Client\ClientInterface
    $myRequestFactory,  // Psr\Http\Message\RequestFactoryInterface  (optional)
    $myStreamFactory,   // Psr\Http\Message\StreamFactoryInterface   (optional)
);
```

Each part falls back to discovery independently, so supplying only the client is fine.

In Laravel this happens for you - the [service provider](/sdks/php/laravel) takes the container's PSR-18 binding when there is one.

## Any implementation works

```sh theme={null}
composer require guzzlehttp/guzzle
```

```sh theme={null}
composer require symfony/http-client nyholm/psr7
```

<Note>
  Both are verified on every commit. One CI job installs the package into an empty project and fails if any HTTP stack comes with it; another runs the SDK end to end against Symfony HttpClient with Guzzle absent.
</Note>

## What gets installed

Installing `westyx/nexus` brings 6 packages, about 1 MB:

| Package              | Purpose                                                 |
| -------------------- | ------------------------------------------------------- |
| `psr/http-client`    | PSR-18 client interface                                 |
| `psr/http-factory`   | PSR-17 request/stream factory interfaces                |
| `psr/http-message`   | PSR-7 message interfaces                                |
| `psr/log`            | PSR-3 logger interface                                  |
| `php-http/discovery` | Finds the PSR-18/PSR-17 implementation your project has |

## Status codes

PSR-18 clients **return** 4xx and 5xx responses rather than throwing, and the SDK maps every status to a typed exception itself.

<Warning>
  If you previously wrapped SDK calls in Guzzle-specific error handling, `GuzzleHttp\Exception\*` no longer reaches your code from the SDK. Catch `WestyxNexus\Exceptions\NexusException` and its subclasses instead - see [Error handling](/sdks/php/error-handling).
</Warning>

The mapping is shared by every endpoint, so a condition reports identically wherever it occurs: a `429` on an A/B evaluation carries its `Retry-After` and its quarantine details exactly as one on a sync does.

## Response size

Every response is read with a hard byte limit enforced *while* reading - 10 MB for a sync, 1 MB for everything else. Exceeding it raises a size error naming the limit, rather than handing a truncated document to the JSON parser.

## Live updates are the exception

PSR-18's `sendRequest()` returns a complete `ResponseInterface`, and whether the body is buffered before returning is left to the implementation. There is no standard way to request incremental delivery, so a buffering client would block forever on an endless event stream.

[`connectStream()`](/sdks/php/sse-live-updates) therefore uses **`ext-curl`** directly, which is also what gives it a real connection deadline and a liveness deadline. Without `ext-curl` it raises an actionable error and everything else, TTL polling included, works normally - losing live updates is a degradation, not a failure.
