Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Swagger (OpenAPI) と PHPStan で REST API でも型安全っぽく使う
Search
kalibora
November 26, 2019
Programming
3.5k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Swagger (OpenAPI) と PHPStan で REST API でも型安全っぽく使う
Swagger と swagger-php(+自作拡張)と swagger-codegen と phpstan を使ってやや型安全に開発をしている話。
kalibora
November 26, 2019
More Decks by kalibora
See All by kalibora
QA環境で誰でも自由自在に現在時刻を操って検証できるようにした話
kalibora
0
530
PHPのアノテーション(アトリビュート)からOpenAPIのドキュメントを出力し、レスポンスもそれを元にシリアライズすることで仕様と実装を乖離させず、色々楽できたよって話
kalibora
0
260
Symfony2 の Functional Test のメモリ使用量と実行時間を削減した話
kalibora
0
22
WebAudioと音の話
kalibora
0
450
Other Decks in Programming
See All in Programming
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
510
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
180
FDEが実現するAI駆動経営の現在地
gonta
2
190
Welcome to the "Parametricity" 🏙️ − Generic だけど Specific な世界 −
guvalif
PRO
1
180
php-fpmのプロセスが枯渇した日-調査・対処・そして本当にやるべきだったこと-
shibuchaaaan
0
110
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
140
Embedded SREと共に達成した会員管理システムのAWS移行 - SRE NEXT 2026 ランチスポンサーセッション
niftycorp
PRO
1
2.8k
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
360k
音楽のための関数型プログラミング言語mimiumにおける多段階計算の活用
tomoyanonymous
1
350
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
150
20260623_Loop Engineeringで自分の分身の問い合わせBotを作る
ryugen04
0
230
【やさしく解説 設計編・中級 #6】良いアーキテクチャとは ~ 一本の登り道の、行き先 ~
panda728
PRO
0
170
Featured
See All Featured
The Spectacular Lies of Maps
axbom
PRO
1
870
How to Ace a Technical Interview
jacobian
281
24k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
220
Producing Creativity
orderedlist
PRO
348
40k
Into the Great Unknown - MozCon
thekraken
41
2.6k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
How STYLIGHT went responsive
nonsquared
100
6.2k
A Modern Web Designer's Workflow
chriscoyier
698
190k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.5k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
Product Roadmaps are Hard
iamctodd
55
12k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Transcript
Swagger (OpenAPI) と PHPStan で REST API でも型安全っぽく使う
⾃⼰紹介 ID: @kalibora 所属: 株式会社オトバンク オーディオブック(⽿で聴く本)を作ってる会社 サーバーサイド(API, batch, web ...
)の開発をしております 今⽇話すのは API の開発をこういう感じでやってるけど、なかなかいいよ という共有の話です
おさらい : Swagger とは RESTful Web サービスの設計、構築、⽂書化、使⽤に役⽴つツール で、⼤規模なエコシステムによってバックアップされたオープンソー スのソフトウェアフレームワーク。 で、その中にある
Swagger Specification (Swagger 仕様)に関して は、 OpenAPI Specification に改名された。
実際に OpenAPI で仕様書書いてる⼈どれくら いいますか? swagger: "2.0" info: description: " こういうやつですね"
version: "1.0.0" title: "Swagger Petstore" termsOfService: "http://swagger.io/terms/" contact: email: "
[email protected]
" license: name: "Apache 2.0" url: "http://www.apache.org/licenses/LICENSE-2.0.html" host: "petstore.swagger.io" basePath: "/v2"
ぶっちゃけ OpenAPI で仕様書くの、ダルくな いですか?
OpenAPI 仕様を書くための⼿段 Swagger Editor ブラウザベースでOpenAPI を記述できるエディター https://editor.swagger.io/ 普通はこれを使う(のかな) swagger-php アノテーションベースでOpenAPI
仕様を記述できる https://github.com/zircote/swagger-php ダルさを解消するために、これを拡張して使っております
swagger-php (v2) の使⽤例 例えば User というクラスに name プロパティがあって、 これをAPI のレスポンスのI/F
として定義したい場合、 /** * @SWG\Definition() */ class User { /** * ユーザー名 * @var string * @SWG\Property() */ public $name; }
出⼒結果 ↑ 先程のアノテーションを解析して、下記のようなOpenAPI 仕様が書 き出されます。 swagger: '2.0' definitions: User: properties:
name: description: ユーザー名 type: string
でもまだダルい
まだダルポイントその 1 getter などのアクセサに対応していない 実際の業務で使っているエンティティクラスは private なプロパ ティ + getter
の構成が多いのでこのままだとうまく使えない
対策 1: swagger-php を拡張して getter に対応した /** * @SWG\Definition() */
class User { private $name; /** * ユーザー名 * @SWG\Property() */ public function getName() : string { return $this->name; } }
出⼒結果 先程とほぼ同じ結果のOpenAPI 仕様が書き出されるように拡張。 return type hint を使い、nullable ではないものは required に。
swagger: '2.0' definitions: User: required: - name properties: name: description: ユーザー名 type: string
でもやっぱりまだダルい
まだダルポイントその 2 そもそもだけど、アノテーション書いたらそのままAPI の挙動にも 反映したい アノテーションから 仕様(OpenAPI 仕様の出⼒結果) 実装(実際にAPI のレスポンスとして返す値)
この両⽅に影響を与えたい
対策 2: オブジェクトから配列に変換する処理を書いた /** * @SWG\Definition() */ class User {
private $name; /** * ユーザー名 * @SWG\Property() */ public function getName() : string { return $this->name; } }
連想配列に変換 SwaggerSerializer という⾃前で実装したクラスが @SWG\Property() アノテーションを読み取り、連想配列にする。 $user = new User('Otobank Taro');
var_dump($swaggerSerializer->serialize($user)); // [name => 'Otobank Taro'] これにより、 @SWG\Property() を定義した getter は API のレスポンス のI/F としても露出するし、実際にAPI のレスポンスとしても返却され るようにした。
PHPStan と組み合わせる
おさらい : PHPStan とは PHP の静的解析ツールの1 つ コードを実⾏せずとも分かる、いろんなエラーを検出してくれる そのためには型が重要になる
例えばこんなのは class Main { public function execute() : void {
echo Util::getTomorrow(new \DateTimeImmutable('1980-01-01')) ->format('Y/m/d'), PHP_EOL; } } class Util { public static function getTomorrow(\DateTimeImmutable $today) : ?\DateTimeImmutable { // ノストラダムスの⼤予⾔によって世界が滅びるので明⽇は無い $endDayOfTheWorld = new \DateTimeImmutable('1999-07-31'); return ($today >= $endDayOfTheWorld) ? null : $today->modify('+1 day'); } }
こうなる $ ./vendor/bin/phpstan analyse src --level=max 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% ------
-------------------------------------------------------- Line Main.php ------ -------------------------------------------------------- 4 Cannot call method format() on DateTimeImmutable|null. ------ -------------------------------------------------------- [ERROR] Found 1 error getTommorow() の戻り値が nullable なのに null チェックをしないで format() メソッドを使⽤しているため。
と⾔うわけで、例えばこんなケース
API 側のエンティティクラス class Campaign { /** * キャンペーン開始⽇時 * @SWG\Property()
*/ public function getStartedAt() : \DateTimeInterface { /* 実装は省略 */ } /** * キャンペーン終了⽇時 * @SWG\Property() */ public function getEndedAt() : ?\DateTimeInterface { /* 省略 */ } }
出⼒される OpenAPI 仕様書 swagger: '2.0' definitions: Campaign: required: - startedAt
properties: startedAt: description: キャンペーン開始⽇時 type: string format: date-time endedAt: description: キャンペーン終了⽇時 type: string format: date-time x-nullable: true
API を呼び出すクライアント側 OpenAPI 仕様から⾃動的にAPI クライアントを作るツールは⾊々あ るので好きなものを使えば良いと思う 今回 OpenAPI 仕様で required
などを⽤い nullable か否かをきちん と出⼒するようにしたので、それを加味して⽣成するツールであれ ば良い 加味しない場合でもクラス⽣成のテンプレートをカスタマイズでき たりするので、それを使うことも可能 弊社では swagger-api/swagger-codegen + テンプレートをカスタマ イズして nullable かどうかも加味するようにしている
⾃動⽣成されたクラス class Campaign implements ModelInterface, ArrayAccess { /** * Gets
started_at * @return \DateTime */ public function getStartedAt() { /* 実装は省略 */ } /** * Gets ended_at * @return \DateTime|null */ public function getEndedAt() { /* 実装は省略 */ } }
PHPStan でのチェック クライアント側で⾃動⽣成されたクラスでも ちゃんと getEndedAt() が nullable になっているので、 API を呼び出す側の実装者が、うっかりキャンペーンの終了⽇時が
nullable である。 という仕様を知らなかった(読み取り損ねた)としても、⾃動的にエ ラーを検出可能。
まとめ
まとめ(ツール) API 側 zircote/swagger-php getter にも適⽤する拡張(type hint で nullable も加味)
アノテーションからオブジェクトを連想配列にする実装(仕様 と実装をあわせる) クライアント側 swagger-api/swagger-codegen テンプレートをちょっとカスタマイズ(nullable も加味) phpstan/phpstan
まとめ(図)