PHP is the workhorse of the Internet. From meta frameworks like Laravel that are relentlessly practical and focused on real-world use cases, to the estimated 810 million websites running on Wordpress, chances are you will use a web app powered by PHP today. API providers who want to …
PHP is the workhorse of the Internet. From meta frameworks like Laravel that are relentlessly practical and focused on real-world use cases, to the estimated 810 million websites running on Wordpress, chances are you will use a web app powered by PHP today. API providers who want to drive adoption of their tools would do well to consider the needs of PHP developers, given the critical role of this technology.
That’s why we’re so excited to announce that the PHP SDK generator is now generally available. Read on to learn more about how we designed the PHP SDK generator, and about teams already using it in production.
Consistent foundations
SDKs are, in a way, like footwear: when you reach for a pair of shoes, you want something that makes you go faster and insulates you from rough surfaces. And generally, once you’ve learned how to tie your laces, you should never have to re-learn how to tie them again.
Likewise, a design goal we have for all of our generated SDKs is that existing conventions and assumptions should carry over from the target language to our generated SDKs. At the same time, more complex use cases should be possible even if the language lacks native support.
Let’s begin by examining a basic request and response in one of our generated SDKs. A common scenario for users of the Anthropic SDK is to send user content to Claude and get a streaming response with a series of events representing the LLM’s deliberations.
<?php
use Anthropic\Client;
$client = new Client;
$stream = $client->messages->createStream(
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
model: 'claude-sonnet-4-5-20250929',
);
Each event received is parsed into a distinct value object instance which corresponds to the shape of the data.
<?php
foreach ($stream as $event) {
switch (true) {
case $event instanceof RawMessageStartEvent:
var_dump($event->message);
break;
case $event instanceof RawMessageDeltaEvent:
var_dump($event->delta);
break;
default:
var_dump($event->type);
break;
}
}
Design considerations
At a high level, these value objects are the fundamental building blocks of the Anthropic PHP SDK. While seemly simple, the design of this data structure encapsulates several important design decisions:
Should request value objects and response value objects share the same type?
When constructing request objects, should raw arrays be allowed as well as value objects?
How should value objects handle unknown response values?
How should value objects cope with breaking changes in the API?
How should value objects handle custom request values that don’t conform to their existing structure or type?
How should value objects handle serialization and deserialization of ambiguous data structures?
As we build beyond the basic value object abstraction, we must reconcile all of these considerations (and more!) to make sure we end up with harmony between the API, the library, and the language itself.
To illustrate: Typically LLM oriented APIs will have a Message data shape, which holds context about a conversational thread, with two pieces of information: the original speaker, and the content of the speech. That content can be text, but is commonly multi-modal, with images, audio, and other supported exchange formats.
Here’s Anthropic’s response Message class:
<?php
final class Message implements BaseModel
{
use SdkModel;
#[Required]
public string $role = 'assistant';
#[Required(list: ContentBlock::class)]
public array $content;
}
This API shape supports several use cases:
In Anthropic’s conversational API, the Message’s shape is present in both the input and output positions (this style of API design is not uncommon in other contexts as well).
Often, the input Message and the output Message data shapes will be slightly different, resulting in nominally different value object classes. Consequently, users will want to be able to conveniently convert to and from each, or better yet, can be used in-place of each other.
Taking it a step further, if the user specified their data with PHP arrays instead of Message objects, they should not be concerned with the input and output bifurcation.
Accordingly, the PHP SDKs we generate explicitly allow and encourage developers to pass either value objects or PHP arrays wherever input data is accepted.
<?php
$message = $client->messages->create(
messages: [MessageParam::with(role: 'user', content: 'Hello, Claude')],
);
$message = $client->messages->create(
messages: [['role' => 'user', 'content' => 'Hello, Claude']],
);
Reaping the rewards
As users encounter value objects in SDK responses, they naturally learn how to use them and can apply that same intuition when providing input elsewhere.
For example, for paginated endpoints, each page returned is just another value object composed with a basic pagination interface. Convenience methods are provided for retrieving each item in a page, and successive items on all pages:
<?php
$page = $client->beta->messages->batches->list(limit: 20);
foreach ($page->getItems() as $item) {
var_dump($item->id);
}
foreach ($page->pagingEachItem() as $item) {
var_dump($item->id);
}
Looking at the Pageclass, you can see it’s a value object with added functionality:
<?php
final class Page implements BaseModel, BasePage
{
use SdkModel;
use SdkPage;
#[Optional(list: 'mixed')]
public ?array $data;
#[Optional('has_more')]
public ?bool $hasMore;
#[Optional('first_id', nullable: true)]
public ?string $firstID;
#[Optional('last_id', nullable: true)]
public ?string $lastID;
}
Once a user is familiar with value objects, the incremental cost to pick up paginated interactions is minimized. Likewise, for advanced use cases like specifying custom request options, nothing seems unusual or out of place:
<?php
$client = new Client(requestOptions: ['maxRetries' => 0]);
$result = $client->messages->create(..., requestOptions: ['maxRetries' => 6]);
$client = new Client(requestOptions: RequestOptions::with(maxRetries: 7)]);
Good designs are deliberately boring. Just like a great pair of shoes, a great SDK is something you shouldn’t really notice. We strive to make our SDKs blend seamlessly into the conventions and best practices of the target programming language so they’re never in the way and are always accelerating work.
Trusted by teams in production
The strongest proof of our SDK quality is real teams shipping production systems with them. Here are a few companies using Stainless-generated PHP SDKs today:
With our PHP SDK generator, you get strong types without losing the flexibility of arrays. Value objects that act like normal PHP data. Pagination and streaming that compose naturally with foreach. Escape hatches that don’t fight the type system. And tooling that improves correctness without forcing ceremony.
Under the hood, we enforce consistency across models, pages, streams, and request options. On the surface, everything reads like ordinary PHP.
This balance is the core of the Stainless PHP SDK generator: modern typing, predictable behavior, and minimal conceptual overhead.
If you’d like to dig deeper, explore the changelog or create a Stainless account and start generating SDKs today.
We’re excited to see what you build, and we’d love to hear your feedback if you add PHP as a supported language!