trust-medical / laravel-chatwork-api
A Laravel package for the Chatwork API v2 (HTTP client, Notification channel, OAuth2).
Package info
github.com/trust-medical/laravel-chatwork-api
pkg:composer/trust-medical/laravel-chatwork-api
Requires
- php: ^8.3
- composer-runtime-api: ^2.0
- illuminate/cache: ^11.0 || ^12.0 || ^13.0
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/notifications: ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- psr/http-message: ^1.1 || ^2.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
This package is auto-updated.
Last update: 2026-05-25 07:22:28 UTC
README
Chatwork API v2 を Laravel から安全に利用するための Composer パッケージです。Facade / DI / Laravel Notification の3経路を公式サポートします。
- PHP
^8.3 - Laravel
^11.0 || ^12.0 || ^13.0 - Chatwork API v2(Base URI:
https://api.chatwork.com/v2)
特長
- すべてのエンドポイント(rooms / messages / members / tasks / files / links / contacts / me / my / incoming_requests)をカバー
- API Token と OAuth2 Bearer Token の両認証に対応(排他は型で構造的に保証)
- 戻り値モードを呼び出し側でチェーン切り替え(DTO / 配列 / Collection / Response / Result)
readonlyResponse DTO と immutable Request オブジェクトによる型安全な API- Laravel Notification channel(
ChatworkChannel)を同梱 - OAuth2 認可フロー(認可URL生成・callback・refresh token、
Cache::lockによる多重 refresh 防止)
インストール
composer require trust-medical/laravel-chatwork-api
ServiceProvider と Facade は Laravel のパッケージ自動検出で登録されるため、手動登録は不要です。
設定ファイルを publish します:
php artisan vendor:publish --tag="chatwork-config"
.env に最低限の認証情報を設定します:
CHATWORK_API_TOKEN=your-api-token
設定
config/chatwork.php の主なキー:
| キー | 既定値 | 説明 |
|---|---|---|
default |
default |
使用する connection 名(CHATWORK_CONNECTION) |
base_uri |
https://api.chatwork.com/v2 |
API ベース URI |
timeout |
10 |
リクエストタイムアウト秒 |
response.mode |
dto |
既定の戻り値モード(CHATWORK_RESPONSE_MODE。無効値は ChatworkConfigurationException) |
connections |
API Token connection 1件 | 複数 connection 定義可 |
oauth |
— | OAuth2 設定(後述) |
oauth.timeout |
10 |
OAuth トークン要求のタイムアウト秒(CHATWORK_OAUTH_TIMEOUT) |
複数 connection の例:
'connections' => [ 'default' => [ 'auth' => 'api_token', 'token' => env('CHATWORK_API_TOKEN'), ], 'bot' => [ 'auth' => 'api_token', 'token' => env('CHATWORK_BOT_TOKEN'), ], ],
基本的な使い方
use TrustMedical\LaravelChatworkApi\Facades\Chatwork; // メッセージ送信 Chatwork::rooms()->messages()->create(123, 'こんにちは'); // 自分の情報を取得(既定は DTO で返る) $me = Chatwork::me()->get(); echo $me->name;
connection の切り替え
// 設定済み connection を名前で指定 Chatwork::connection('bot')->rooms()->messages()->create(123, 'bot からの通知'); // その場限りのトークンで実行 Chatwork::withApiToken($token)->me()->get(); Chatwork::withBearerToken($oauthAccessToken)->me()->get();
connection() / withApiToken() / withBearerToken() / as*() は新しい manager を返すイミュータブル設計のため、安全にチェーンできます。ChatworkManager はコンテナ singleton ですが、これらは共有インスタンスを mutate せず clone を返すため、Laravel Octane / Swoole / キューワーカー等の常駐プロセスでもリクエスト間で connection・認証情報・戻り値モードが漏れません。
戻り値モード
既定は asDto()。呼び出し側でチェーンして変更できます。
| モード | 成功時 | 4xx / 5xx |
|---|---|---|
asDto() |
readonly DTO | ChatworkRequestException を throw |
asArray() |
配列 | ChatworkRequestException を throw |
asCollection() |
Illuminate\Support\Collection |
ChatworkRequestException を throw |
asResponse() |
Laravel HTTP Response | throw しない |
asPsrResponse() |
PSR-7 Response | throw しない |
asResult() |
Result(Http\Result) |
throw しない |
$rooms = Chatwork::asCollection()->rooms()->list(); $raw = Chatwork::asArray()->me()->get(); $res = Chatwork::asResponse()->rooms()->find(123);
送信前バリデーション失敗は戻り値モードに関わらず常に
ChatworkValidationExceptionを throw します。
メソッド別の戻り値型(asDto() 契約)
各 Resource メソッドのネイティブ署名は : mixed です(戻り値型は ResponseMode
により実行時に変わるため)。下表は 既定の asDto() モード での戻り値型です。
asArray() / asCollection() / asResponse() / asPsrResponse() / asResult()
へ切り替えた場合は宣言型・下表の型と実行時型が乖離し、その型解釈は呼び出し側の
責務になります(設計判断の詳細は docs/03-package-architecture/response-strategy.md)。
| メソッド | asDto() 戻り値型 |
|---|---|
rooms()->list() |
list<RoomData> |
rooms()->create() |
CreatedRoom |
rooms()->find() |
RoomData |
rooms()->update() |
UpdatedRoom |
rooms()->leaveRoom() / deleteRoom() |
NoContentData |
rooms()->messages()->create() |
CreatedMessage |
rooms()->messages()->list() |
list<MessageData> |
rooms()->messages()->find() |
MessageData |
rooms()->messages()->update() |
UpdatedMessage |
rooms()->messages()->deleteMessage() |
DeletedMessage |
rooms()->messages()->markAsRead() |
MarkReadResult |
rooms()->messages()->markAsUnread() |
MarkUnreadResult |
rooms()->members()->list() |
list<RoomMemberData> |
rooms()->members()->replaceMembers() |
ReplacedRoomMembers |
rooms()->tasks()->list() |
list<RoomTaskData> |
rooms()->tasks()->create() |
CreatedTask |
rooms()->tasks()->find() / updateStatus() |
RoomTaskData |
rooms()->files()->list() |
list<RoomFileData> |
rooms()->files()->upload() |
UploadedRoomFile |
rooms()->files()->find() |
RoomFileData |
rooms()->links()->find() / create() / update() / deleteLink() |
RoomLinkData |
contacts()->list() |
list<ContactData> |
me()->get() |
MyAccountData |
my()->status() |
MyStatusData |
my()->tasks() |
list<MyTaskData> |
incomingRequests()->list() |
list<IncomingRequestData> |
incomingRequests()->accept() |
ContactData |
incomingRequests()->decline() |
NoContentData |
list<…> 系で Chatwork が 204 を返す場合、asDto() では [] に縮退します
(contacts()->list() / my()->tasks() / incomingRequests()->list())。
リソース別の例
Rooms
use TrustMedical\LaravelChatworkApi\Data\Requests\CreateRoomRequest; use TrustMedical\LaravelChatworkApi\Data\Requests\UpdateRoomRequest; use TrustMedical\LaravelChatworkApi\Enums\IconPreset; Chatwork::rooms()->list(); Chatwork::rooms()->find(123); Chatwork::rooms()->create(new CreateRoomRequest( name: '新規ルーム', membersAdminIds: [101, 102], description: 'チーム連絡用', iconPreset: IconPreset::Meeting, )); Chatwork::rooms()->update(123, new UpdateRoomRequest(name: '改名後')); // 破壊的操作は対象を明示した命名(曖昧な delete() は提供しない) Chatwork::rooms()->leaveRoom(123); // action_type=leave Chatwork::rooms()->deleteRoom(123); // action_type=delete
Messages
Chatwork::rooms()->messages()->create(123, '本文', selfUnread: true); Chatwork::rooms()->messages()->list(123, force: true); Chatwork::rooms()->messages()->find(123, '1024'); Chatwork::rooms()->messages()->update(123, '1024', '編集後の本文'); Chatwork::rooms()->messages()->deleteMessage(123, '1024'); Chatwork::rooms()->messages()->markAsRead(123, '1024'); Chatwork::rooms()->messages()->markAsUnread(123, '1024');
Members
use TrustMedical\LaravelChatworkApi\Data\Requests\ReplaceRoomMembersRequest; Chatwork::rooms()->members()->list(123); Chatwork::rooms()->members()->replaceMembers(123, new ReplaceRoomMembersRequest( membersAdminIds: [101], membersMemberIds: [201, 202], membersReadonlyIds: [301], ));
Tasks
use TrustMedical\LaravelChatworkApi\Data\Requests\CreateRoomTaskRequest; use TrustMedical\LaravelChatworkApi\Enums\LimitType; use TrustMedical\LaravelChatworkApi\Enums\TaskStatus; Chatwork::rooms()->tasks()->create(123, new CreateRoomTaskRequest( body: '見積もりを確認する', toIds: [101, 102], limit: 1735718400, limitType: LimitType::Time, )); Chatwork::rooms()->tasks()->list(123, status: TaskStatus::Open); Chatwork::rooms()->tasks()->find(123, 456); Chatwork::rooms()->tasks()->updateStatus(123, 456, TaskStatus::Done);
Files
use TrustMedical\LaravelChatworkApi\Data\Requests\UploadRoomFileRequest; Chatwork::rooms()->files()->upload(123, new UploadRoomFileRequest( path: storage_path('app/report.pdf'), message: '月次レポートです', )); Chatwork::rooms()->files()->list(123); Chatwork::rooms()->files()->find(123, 789, createDownloadUrl: true);
Invitation Links
use TrustMedical\LaravelChatworkApi\Data\Requests\RoomLinkRequest; Chatwork::rooms()->links()->find(123); Chatwork::rooms()->links()->create(123, new RoomLinkRequest( code: 'team-invite', needAcceptance: true, description: '招待リンク', )); Chatwork::rooms()->links()->update(123, new RoomLinkRequest(description: '説明更新')); Chatwork::rooms()->links()->deleteLink(123);
Me / My / Contacts / Incoming Requests
Chatwork::me()->get(); Chatwork::my()->status(); Chatwork::my()->tasks(status: TaskStatus::Open); Chatwork::contacts()->list(); Chatwork::incomingRequests()->list(); Chatwork::incomingRequests()->accept(456); Chatwork::incomingRequests()->decline(456);
エラーハンドリング
例外
| 例外 | 発生条件 |
|---|---|
ChatworkValidationException |
送信前バリデーション失敗(戻り値モードに関わらず常に throw) |
ChatworkRequestException |
4xx / 5xx(throw 系モード時) |
ChatworkAuthenticationException |
認証情報の解決失敗(connection 不正・OAuth refresh 失敗等) |
ChatworkConfigurationException |
設定・配線が不正(oauth.state_store / oauth.token_repository に不正クラス、oauth.route_throttle 形式不正、base_uri スキーム不正など) |
すべての例外は marker interface ChatworkException を実装します。本パッケージ由来の例外を一括捕捉したい場合は catch (ChatworkException $e) が使えます(status() / violations() などの固有メソッドは具象例外型で分岐してください)。
use TrustMedical\LaravelChatworkApi\Exceptions\ChatworkException; try { Chatwork::rooms()->messages()->create(123, '本文'); } catch (ChatworkException $e) { // 本パッケージ由来の全例外をここで捕捉 }
ChatworkRequestException はエラーボディ2系統を取り出せます:
use TrustMedical\LaravelChatworkApi\Exceptions\ChatworkRequestException; try { Chatwork::rooms()->messages()->create(123, '本文'); } catch (ChatworkRequestException $e) { $e->status(); // int $e->errors(); // string[](通常 API: {"errors":[...]}) $e->error(); // ?string(OAuth: error) $e->errorDescription(); // ?string(OAuth: error_description) $e->rateLimit(); // ?array(429 時: limit / remaining / reset) }
asResult()(例外を投げない)
$result = Chatwork::asResult()->rooms()->messages()->create(123, '本文'); if ($result->failed()) { $result->status(); // int $result->errors(); // string[] $result->rateLimit(); return; } $data = $result->data();
Notification チャンネル
Notification から Chatwork へ送信できます。チャンネルは内部的に asResult() 固定で、4xx は permanent failure として例外化、5xx / 429 / ネットワークエラーはそのまま伝播してキュー再試行に委譲されます。
ChatworkMessage はメッセージ組み立て専用のビルダー / DTO です(Notification ではありません)。ChatworkNotification を継承すると via() は自動で ChatworkChannel に接続されるため、toChatwork() を実装するだけで送信できます。
use TrustMedical\LaravelChatworkApi\Notifications\ChatworkMessage; use TrustMedical\LaravelChatworkApi\Notifications\ChatworkNotification; class DeployFinished extends ChatworkNotification { public function toChatwork(object $notifiable): ChatworkMessage { return (new ChatworkMessage()) ->toRoom(123) ->info('デプロイ完了', "本番反映が完了しました。\nコミット: abc123") ->selfUnread(); } }
ChatworkNotification を使わず通常の Notification で via() に [ChatworkChannel::class] を返し、toChatwork($notifiable): ChatworkMessage を実装しても構いません(queueable・複数チャンネル併用などはこちら)。
メッセージビルダーは body() / title() / code() / hr() / plain() / escape() / to()(TO 付与)/ toRoom() / selfUnread() を提供します。
送信先は ChatworkMessage::toRoom() のほか、notifiable 側の routeNotificationForChatwork() でも指定できます(両方指定は競合エラー):
use TrustMedical\LaravelChatworkApi\Notifications\ChatworkRoute; public function routeNotificationForChatwork(): ChatworkRoute { return ChatworkRoute::room(123)->connection('bot'); }
OAuth2
config/chatwork.php の oauth セクションで設定します。
CHATWORK_OAUTH_CLIENT_ID=... CHATWORK_OAUTH_CLIENT_SECRET=... CHATWORK_OAUTH_REDIRECT_URI=https://example.com/chatwork/oauth/callback
Confidential / Public Client の自動判定: Chatwork は OAuth クライアント 種別ごとにトークンエンドポイントの認証方式が異なります。Confidential Client (client_secret あり) は
Authorization: Basic Base64(client_id:client_secret)ヘッダーが必須、Public Client (client_secret なし) は body にclient_idのみを 含めて Basic 認証ヘッダーを送りません。本ライブラリはCHATWORK_OAUTH_CLIENT_SECRETの有無 (空文字含む) で自動判定します。Confidential / Public を切り替える追加 設定キーはありません。
callback ルートは既定で無効です(セキュリティのため)。利用する場合は config/chatwork.php で有効化します:
'oauth' => [ // ... 'routes_enabled' => true, 'route_prefix' => 'chatwork/oauth', 'redirect_after_callback' => '/dashboard', 'token_repository' => \App\Chatwork\DatabaseTokenRepository::class, 'state_store' => null, // 既定は Cache ベース ],
有効化すると GET {route_prefix}/callback(ルート名 chatwork.oauth.callback)が登録されます。callback では state 検証が必須で、token は設定した TokenRepository に保存されます。取得済みトークンは Chatwork::connection('oauth-connection') 経由で利用でき、期限切れ時は Cache::lock で多重発行を防ぎつつ自動 refresh されます。
本番環境の推奨: 既定の
StateStore/TokenRepositoryは Cache ストアを使います。stateの一度きりの消費(リプレイ攻撃防止)には read-and-delete のアトミック性が必要なため、本番ではredisまたはdatabaseキャッシュドライバを使用してください。array/fileドライバは read→delete が非アトミックで、同一stateの二重消費が理論上成立し得ます。永続トークンには独自のTokenRepository(例: DB 実装)を設定することを推奨します。
トークンの暗号化: 既定の
CacheTokenRepositoryは access/refresh トークンを Laravel のEncrypter(APP_KEY)で暗号化してからキャッシュへ保存します。Redis / Memcached を直接参照されてもトークンは平文露出しません。APP_KEY未設定だとMissingAppKeyExceptionになります(通常の Laravel アプリでは設定済み)。APP_KEYをローテーションした場合、暗号化済みの既存トークンは復号できず「未保存」とみなされ、利用者は再認証が必要になります。独自のTokenRepositoryを使う場合は暗号化も自実装の責務です。
callback ルートの throttle
config/chatwork.php の oauth.route_throttle(既定 '10,1' = 1分あたり10回)が callback ルートに throttle ミドルウェアとして適用され、state / code のブルートフォースを抑制します。"max,decayMinutes" 形式の文字列、または名前付き rate limiter 名を指定できます。null / 空文字で throttle を無効化します。形式不正値は ServiceProvider 起動時に ChatworkConfigurationException になります。
独自 RouteServiceProvider からの手動登録
routes_enabled を false のままにしつつ、任意の middleware / ドメイン / プレフィックス配下で callback ルートを登録したい場合は、ChatworkServiceProvider::registerOAuthRoutes() を独自の RouteServiceProvider から呼び出せます(意図的に public)。
route:cache利用時の注意: OAuth callback ルートはpackageBooted()内のクロージャで登録されます。php artisan route:cacheが有効な環境ではこのクロージャが実行されず、callback ルートが登録されず、またroute_throttleの形式検証も行われません。registerOAuthRoutes()を独自RouteServiceProviderから手動呼び出しする場合もroute:cache下では同様にスキップされます。route:cacheを使う環境で OAuth callback を利用する場合は、ルートをアプリ側の実 routes ファイルに明示登録し、route_throttleの指定が実際に効いているか(php artisan route:list等で)確認してください。
テスト
composer test # Pest 全件 composer test:coverage # カバレッジ(目標 80%+) composer analyse # PHPStan composer lint # Pint で整形 composer ci # lint:test + analyse + test
すべてのテストは Http::fake() で実 API を叩かずに検証しています。
セキュリティ
- API token / client secret / refresh token をログ・例外メッセージに含めません
- OAuth2 callback は
state検証を必須とし、ルートは既定で無効です - API Token(
x-chatworktoken)と OAuth2 Bearer(Authorization: Bearer)は1リクエストで同時送信されません(Credentials実装で構造的に保証) - OAuth2
stateのリプレイ防止には read-and-delete のアトミック性が必要なため、本番ではredis/databaseキャッシュドライバを推奨します(array/fileは非アトミック)
脆弱性を発見した場合は公開 issue ではなく非公開でご連絡ください。