Claude Media
Claude PHP SDKの値オブジェクトとバージョン運用

Claude PHP SDKの値オブジェクトとバージョン運用

PHP 8.1以上向け公式SDKの導入・Value Object・PSR-18ストリーミング・エラー処理・SemVer運用まで実装例で解説します。

PHPからClaudeを呼ぶまで — 導入と動作要件

Anthropic公式のPHP SDKは、PHP 8.1.0以上のアプリケーションからClaude APIを呼ぶためのライブラリです。HTTP通信はPSR-18に準拠し、インストール済みのPSR-18クライアントを自動で探します。公式が推奨するのはGuzzleで、追加設定なしにストリーミング向けの構成が入るためです。

このSDKが担うのは、リクエストを組み立てて送り、応答を受け取るところまでです。ツール実行結果を見て次の一手を自律的に判断し続けるエージェントの実行基盤は含みません。そうした自律エージェントはPython・TypeScript向けのAgent SDKやClaude Codeの領分です。Laravelのジョブキューや既存のPHPアプリの一機能として、必要なときにリクエストを送る構成にはPHP SDKが向いています。

PSR-18を採用しているので、LaravelやSymfonyが持つHTTPクライアントをそのまま流用しやすいのも利点です。同じ呼び出しの組み込み例はClaude CodeでWordPressプラグイン開発を始めるにもあります。

導入から最初の応答までは4ステップです。

手順

最初の呼び出しまで

  1. 1

    PHPのバージョンを確かめる

    php --version が8.1.0以上であることを見ます。要件はPHP 8.1.0以上です。

  2. 2

    composer require を実行する

    SDKとGuzzleをまとめて入れます。コマンドは下に載せています。

  3. 3

    APIキーを環境変数に置く

    ANTHROPIC_API_KEY を設定すると、new Client() が自動で読み込みます。

  4. 4

    messages->createを呼ぶ

    maxTokens・messages・model を渡し、返ってきた content からテキストブロックを取り出します。

v0.54.0を入れたときのComposerの出力(抜粋)は次のとおりです。

composer require "anthropic-ai/sdk" "guzzlehttp/guzzle:^7"
  - Installing guzzlehttp/guzzle (7.15.5): Extracting archive
  - Installing anthropic-ai/sdk (v0.54.0): Extracting archive
Using version ^0.54.0 for anthropic-ai/sdk

生成された composer.json には "anthropic-ai/sdk": "^0.54.0" が書かれました。0.x系のキャレット指定は次のマイナー版を含まないため、^0.54.0 は0.54.x系に固定する指定です。マイナー版ごとにAPIが変わりうるSDKなので、この既定の書かれ方がそのまま安全側に働きます。

最初の呼び出しは次のとおりです。

$client = new Client();
 
$message = $client->messages->create(
  maxTokens: 1024,
  messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  model: 'claude-opus-5-5',
);
 
$textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
echo $textBlock->text;

array_find はPHP 8.4で加わった組み込み関数です。PHP 8.1〜8.3では未定義なので、foreach で content を回し、type が text のブロックを取り出します。

APIキーが個人アカウントキーやサービスアカウントキーで、複数のワークスペースにアクセスできる場合は、リクエストヘッダー anthropic-workspace-id でワークスペースを指定します。Workload Identity Federationなど、ほかの認証方式にも対応しています。ワークスペースを限定していないキーでは、リクエストごとに anthropic-workspace-id の指定が必要です。

名前付き引数と値オブジェクトの書き方

公式の説明では、デフォルト値を持つ引数は名前を指定して渡す必要があります。ただしv0.54.0で試すと、必須の3引数(maxTokens・messages・model)は位置引数でも通りました。

// 必須の3引数は位置引数でも HTTP 層まで進む(v0.54.0 で実行)
$client->messages->create(16, [['role' => 'user', 'content' => 'hi']], 'claude-opus-5-5');

接続先を閉じたローカルポートにした環境でも、この呼び出しは型エラーにならず、接続エラーまで進みました。ただし位置は宣言順(maxTokens、messages、model の順)に縛られます。他言語のサンプルを移植するときは、引数の意味が読める名前付きで書き写すほうが安全です。

値オブジェクトは、静的コンストラクタ with と名前付き引数の組み合わせが公式の推奨形です。ビルダー形式でも同じものを組み立てられます。

// 推奨: staticコンストラクタ + 名前付き引数
Base64ImageSource::with(data: "U3RhaW5sZXNzIHJvY2tz", ...);
 
// 代替: ビルダー
(new Base64ImageSource)->withData("U3RhaW5sZXNzIHJvY2tz");

リクエストオプションも with で組み立てます。Anthropic\RequestOptions::with(...) には maxRetries・timeout のほか、ストリーミング用の streamingTransporter を渡せます。クライアント全体の設定にも、リクエスト単位の設定にも使える書き方です。

ビルダー形式を多用してきたコードベースなら withData() の連鎖に揃えられます。引数の順序を覚えたくない場合は with(...) が楽です。どちらでも得られるオブジェクトは同じなので、チームの規約に合わせて選べば足ります。

ストリーミングがまとめて届くときの原因と対処

Server-Sent Eventsによるストリーミングは messages->createStream で扱います。

$client = new Client();
 
$stream = $client->messages->createStream(
  maxTokens: 1024,
  messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  model: 'claude-opus-5-5',
);
 
foreach ($stream as $event) {
  echo $event->type . PHP_EOL;
}

ストリーミングが成立するかどうかは、検出されたPSR-18クライアントがレスポンスボディを段階的に返せるかで決まります。

くらべる

検出されたHTTPクライアントによる違い

段階的に届く

Guzzleが検出される

SDKがストリーミング向けに自動で構成します。foreach にはイベントが届くたびに値が返ります。

一括で届く

バッファリング型のクライアント

レスポンスが完了した時点で、foreach が全イベントをまとめて返します。

イベントが一括で届く症状が出たら、Guzzleを導入するか、streamingTransporter リクエストオプションでストリーミング対応のPSR-18クライアントを渡します。複数のHTTPクライアントを併用しているプロジェクトでは、どれが検出されているかを先に疑います。

$client = new Anthropic\Client(
  requestOptions: Anthropic\RequestOptions::with(streamingTransporter: $myStreamingClient),
);

失敗したときの例外・リトライ・タイムアウト

接続に失敗した場合や、APIが4xx/5xx応答を返した場合は、Anthropic\Core\Exceptions\APIException のサブクラスが投げられます。

use Anthropic\Core\Exceptions\APIConnectionException;
use Anthropic\Core\Exceptions\RateLimitException;
use Anthropic\Core\Exceptions\APIStatusException;
 
try {
  $message = $client->messages->create(
    maxTokens: 1024,
    messages: [['role' => 'user', 'content' => 'Hello, Claude']],
    model: 'claude-opus-5-5',
  );
} catch (APIConnectionException $e) {
  echo "サーバーに接続できませんでした", PHP_EOL;
  echo $e->getPrevious()?->getMessage(), PHP_EOL;
} catch (RateLimitException $_) {
  echo "429を受信しました。少し待ってから再試行します", PHP_EOL;
} catch (APIStatusException $e) {
  echo "200番台以外のステータスを受信しました: " . $e->getMessage();
}

ステータスコードと例外クラスの対応は次の表のとおりです。

ステータス例外クラス
HTTP 400例外クラスBadRequestException
HTTP 401例外クラスAuthenticationException
HTTP 403例外クラスPermissionDeniedException
HTTP 404例外クラスNotFoundException
HTTP 409例外クラスConflictException
HTTP 422例外クラスUnprocessableEntityException
HTTP 429例外クラスRateLimitException
HTTP 500以上例外クラスInternalServerException
上記以外のHTTPエラー例外クラスAPIStatusException
タイムアウト例外クラスAPITimeoutException
ネットワークエラー例外クラスAPIConnectionException

catchの順序と、原因が見えない接続エラー

v0.54.0のクラス階層をPHPの class_parents() で出すと、APITimeoutException は APIConnectionException の子でした。RateLimitException と InternalServerException は APIStatusException の子です。

APITimeoutException  <- APIConnectionException <- APIException <- AnthropicException <- Exception
RateLimitException   <- APIStatusException     <- APIException <- AnthropicException <- Exception
InternalServerException <- APIStatusException  <- APIException <- AnthropicException <- Exception

そのため catch (APIConnectionException $e) はタイムアウトも一緒に受けます。タイムアウトだけ別処理にしたいなら、APITimeoutException の catch を先に書きます。

もう一つの落とし穴は、接続エラーのメッセージの短さです。接続先を http://127.0.0.1:9(待ち受けのないローカルポート)にして messages->create を呼ぶと、getMessage() は Anthropic API Connection Error の1行だけを返しました。原因は getPrevious() の先にあります。

caught Anthropic\Core\Exceptions\APIConnectionException / Anthropic API Connection Error
prev: GuzzleHttp\Exception\ConnectException / cURL error 7: Failed to connect to 127.0.0.1 port 9 after 0 ms: Couldn't connect to server ...

この1行だけでは、失敗の種類が分かりません。ログには getPrevious() の内容も残しておくと、原因を切り分けやすくなります。

リトライの回数と待ち時間

接続エラー・408・409・429・500番台・タイムアウトは、デフォルトで2回まで短い指数バックオフを挟んで自動リトライされます。maxRetries を RequestOptions::with() に渡せば、クライアント全体でもリクエスト単位でも変更や無効化ができます。

use Anthropic\RequestOptions;
 
// クライアント全体の既定を変える
$client = new Client(requestOptions: RequestOptions::with(maxRetries: 0));
 
// リクエスト単位で変える
$result = $client->messages->create(
  maxTokens: 1024,
  messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  model: 'claude-opus-5-5',
  requestOptions: RequestOptions::with(maxRetries: 5),
);

閉じたローカルポートへの接続で、リトライの有無による所要時間を測ると次のようになりました。

数字

接続拒否で失敗するまでの時間(v0.54.0)

  • 既定(リトライ2回)

    1.29秒

    指数バックオフを含む

  • maxRetries: 0

    0.00秒

    1回の失敗で即座に例外

エラーのたびに待ち時間が積み上がるので、画面操作の途中で同期的にClaudeを呼ぶ処理では、既定のままだと失敗の通知が遅れます。外部への書き込みを伴う処理を同じリクエストに含める場合は、リトライで二重実行にならないかも見ておきます。そうしたときに maxRetries: 0 が選択肢になります。

一覧取得のページネーション

一覧取得系のエンドポイントはページネーションされています。SDKは、自動でページを進めるイテレータを返します。

$client = new Client();
 
$page = $client->beta->messages->batches->list(limit: 20);
 
// 現在のページのアイテムを取得
foreach ($page->getItems() as $item) {
  echo $item->id, PHP_EOL;
}
// 現在のページ以降、すべてのページを自動取得
foreach ($page->pagingEachItem() as $item) {
  echo $item->id, PHP_EOL;
}

getItems() は手元のページの分だけを返します。pagingEachItem() は現在のページ以降のすべてのページを取りにいくため、追加のネットワークリクエストが発生します。件数が多い一覧で全件を回すと、その分の通信が走ります。

Bedrock・Vertex・AWS・Foundry経由で使うとき

PHP SDKは次の4つのクラウドプラットフォームに対応しています。

プラットフォームクライアント生成方法・備考
Agent PlatformクライアントAnthropic\Vertex\Client生成方法・備考::fromEnvironment() で生成
Bedrock(新規)クライアントAnthropic\Bedrock\MantleClient生成方法・備考new MantleClient(awsRegion: ...)
Bedrock(既存)クライアントAnthropic\Bedrock\Client生成方法・備考::fromEnvironment() か ::withCredentials()。InvokeModel API利用アプリ向け
Claude Platform on AWSクライアントAnthropic\Aws\Client生成方法・備考aws/aws-sdk-php がソフト依存。ベータ
FoundryクライアントAnthropic\Foundry\Client生成方法・備考::withCredentials() で生成

新規プロジェクトは MantleClient を使い、Anthropic\Bedrock\Client は InvokeModel APIを使う既存アプリのために残されています。クラス名が近いので、Bedrockの記事やコード例を見るときは、どちらのクライアントの話かを先に見分けます。Opus 4.6以前のモデルをBedrockで使う場合は、旧モデル向けの別ページ(claude-on-amazon-bedrock-legacy)が案内されています。

Claude Platform on AWSは、ワークスペースIDを new Anthropic\Aws\Client(workspaceId: ...) か環境変数 ANTHROPIC_AWS_WORKSPACE_ID で指定するベータ機能です。

ドキュメントにないパラメータやエンドポイントを使うとき

ドキュメントに載っていないパラメータの送信や、未文書化のレスポンスプロパティの読み取りは、RequestOptions::with() の extraQueryParams・extraBodyParams・extraHeaders で行えます。

$message = $client->messages->create(
  maxTokens: 1024,
  messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  model: 'claude-opus-5-5',
  requestOptions: RequestOptions::with(
    extraQueryParams: ['my_query_parameter' => 'value'],
    extraBodyParams: ['my_body_parameter' => 'value'],
    extraHeaders: ['my-header' => 'value'],
  ),
);

未文書化のエンドポイントそのものを叩きたい場合は $client->request(...) を使います。認証やリトライなど、クライアントの機能を保ったままリクエストできます。

$client = new Client();
 
$response = $client->request(
  method: "post",
  path: '/undocumented/endpoint',
  query: ['dog' => 'woof'],
  headers: ['useful-header' => 'interesting-value'],
  body: ['hello' => 'world']
);

ベータ版(0.x)のバージョン運用

このSDKはSemVerに従っています。初期開発段階でメジャーバージョンが0のあいだは、APIがいつでも変わりうる扱いです。実行時の挙動に影響しないPHPDocの型定義の改善は、破壊的変更に数えられません。

運用で効くのは、前述の ^0.54.0 のような0.x系のキャレット指定をそのまま保つことです。^0 や * のような緩い制約に書き換えると、マイナー版が上がる更新も自動で取り込まれます。

composer.lock をコミットし、更新は意図したタイミングに限る運用が、ベータ版のSDKには合います。更新前後は composer show anthropic-ai/sdk で版を見比べ、動かなくなった呼び出しがないかを確かめます。

PHP 8.1.0未満をサポートしなければならない既存プロジェクトでは、SDKの動作要件を満たせません。PHPのバージョンアップを先に進めるか、$client->request(...) 相当のHTTP呼び出しをPSR-18クライアントで自前に書くかの選択になります。

まとめ

導入は composer require 1回ですが、運用で差が出るのはHTTPクライアントの選び方、例外の catch 順とリトライ設定、0.x系のバージョン固定の3点です。モデルIDやAPIの挙動は更新されうるので、公式ページのコード例と版を突き合わせてから本番に入れます。

料金・モデル選択・認証まわりの全体像はAnthropic API完全ガイド、コスト最適化の観点ではAnthropic APIのPrompt Cachingを理解する、自律的にツールを連鎖実行するエージェントを組む場合はAgent SDKクイックスタートもあわせて参照してください。

この記事を共有:XはてブLinkedIn