> For the complete documentation index, see [llms.txt](/llms.txt)

# PHP

The PHP SDK is an idiomatic, Composer-installable client emitted from the same `GlottoIR` as every
other target. Files declare `strict_types=1`, models are typed-property objects with `fromJson`,
and PHPDoc is generated from the operation prose in the spec.

Manual pages expose typed items, the full response and explicit continuation. A trailing `RequestOptions` controls headers, deadlines, retries, extra parameters and cooperative cancellation. Binary operations return an owned `Core\BinaryDownload` with bounded `readAll`, `pipe`, response metadata and `close`; `foreach` closes on break or error. See [pagination](/docs/pagination), [request retries and controls](/docs/retries), [streaming](/docs/streaming), and [file transfers](/docs/file-transfers).

## Quickstart

```bash
composer require your-org/petstore
```

```php
<?php
require 'vendor/autoload.php';

$client = new Petstore\Client(token: '<token>');
$result = $client->pets->get('...');
var_dump($result);
```

## Resource objects

Operations are camelCase methods on resource objects — `$client->pets->get($id)`,
`$client->pets->photos->add(...)` — the Stripe-PHP service-accessor shape, rather than flat
snake_case methods.

## PSR-18 transport

The client sends requests through an injectable **PSR-18** HTTP client (Guzzle by default), so the
transport is swappable and mockable — the PHP-ecosystem standard, the analogue of C#'s injectable
`HttpClient`.

## Typed errors

Non-2xx responses throw `ApiError`, an `\Exception` hierarchy carrying the parsed, typed error
body; discriminated-union response bodies resolve to the right concrete type. See
[Errors](/docs/errors).

## Pagination

List methods return a lazy iterator (`foreach`) that walks every page, advancing the cursor for
you. See [Pagination](/docs/pagination).

## Retries & backoff

Transient failures (`5xx`, `429`, transport errors) retry with exponential backoff and jitter,
configurable per client. See [Retries & timeouts](/docs/retries), [Streaming](/docs/streaming),
and [Authentication](/docs/authentication).

## Unknown response fields

Your API can add a response field without it being a breaking change — but a generated class has no promoted property to put it in, and its `fromJson` factory reads only the fields it declares. The generated
models keep it instead: a field the SDK wasn't generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.

```php
$pet = $client->pets->createPet($body);

// A field your API started returning after this SDK was generated.
$species = $pet->extraFields()['species'] ?? null;

// Re-encoding preserves it — a read-modify-write never silently drops it.
$json = json_encode($pet);
```

`extraFields()` returns an `array<string, mixed>`, so nested objects and lists survive intact, and
retention is recursive.

One caveat from PHP's JSON decoder: `json_decode($body, true)` represents an empty JSON object and
an empty array identically, so an unknown field whose value is `{}` round-trips as `[]`. Every
other shape — nested objects, lists, scalars, `null` — round-trips unchanged.

Your existing construction still compiles: the storage is a private field assigned after
construction, so the promoted constructor is untouched.

The retained fields are read-only by design. To *send* a field your spec doesn't model yet, use the
per-call extra-body escape hatch rather than writing to the retained bag.
