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, request retries and controls, streaming, and file transfers.
Quickstart
composer require your-org/petstore
<?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.
Pagination
List methods return a lazy iterator (foreach) that walks every page, advancing the cursor for
you. See Pagination.
Retries & backoff
Transient failures (5xx, 429, transport errors) retry with exponential backoff and jitter,
configurable per client. See Retries & timeouts, Streaming,
and 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.
$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.
Missing and null fields
By default an optional property that decodes from JSON null is null, the same as an omitted
key, and jsonSerialize() drops it — so a read-modify-write sends a decoded null back as an
absent key. Set targets.php.model_presence: explicit to keep
missing, null and a value apart: optional arguments default to the ModelFieldMissing::Value
singleton, null means JSON null, and a generated copy method sets or removes fields. That changes
property and constructor types; the generated README shows the idioms.