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
0
3.2k
Swagger (OpenAPI) と PHPStan で REST API でも型安全っぽく使う
Swagger と swagger-php(+自作拡張)と swagger-codegen と phpstan を使ってやや型安全に開発をしている話。
kalibora
November 26, 2019
Tweet
Share
More Decks by kalibora
See All by kalibora
QA環境で誰でも自由自在に現在時刻を操って検証できるようにした話
kalibora
0
390
PHPのアノテーション(アトリビュート)からOpenAPIのドキュメントを出力し、レスポンスもそれを元にシリアライズすることで仕様と実装を乖離させず、色々楽できたよって話
kalibora
0
200
Symfony2 の Functional Test のメモリ使用量と実行時間を削減した話
kalibora
0
13
WebAudioと音の話
kalibora
0
430
Other Decks in Programming
See All in Programming
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
440
monorepo の Go テストをはやくした〜い!~最小の依存解決への道のり~ / faster-testing-of-monorepos
convto
2
500
Software Architecture
hschwentner
6
2.3k
Devoxx BE 2025 Loom lab
josepaumard
0
100
他言語経験者が Golangci-lint を最初のコーディングメンターにした話 / How Golangci-lint Became My First Coding Mentor: A Story from a Polyglot Programmer
uma31
0
250
iOSでSVG画像を扱う
kishikawakatsumi
0
110
Le côté obscur des IA génératives
pascallemerrer
0
150
Web フロントエンドエンジニアに開かれる AI Agent プロダクト開発 - Vercel AI SDK を観察して AI Agent と仲良くなろう! #FEC余熱NIGHT
izumin5210
3
560
20251016_Rails News ~Rails 8.1の足音を聴く~
morimorihoge
2
510
あなたとKaigi on Rails / Kaigi on Rails + You
shimoju
0
170
SwiftDataを使って10万件のデータを読み書きする
akidon0000
0
180
Cursorハンズオン実践!
eltociear
2
1.1k
Featured
See All Featured
Fireside Chat
paigeccino
40
3.7k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
10
870
Testing 201, or: Great Expectations
jmmastey
45
7.7k
Into the Great Unknown - MozCon
thekraken
40
2.1k
Why You Should Never Use an ORM
jnunemaker
PRO
59
9.6k
Music & Morning Musume
bryan
46
6.8k
Balancing Empowerment & Direction
lara
5
690
Large-scale JavaScript Application Architecture
addyosmani
514
110k
Making Projects Easy
brettharned
120
6.4k
Typedesign – Prime Four
hannesfritz
42
2.8k
How STYLIGHT went responsive
nonsquared
100
5.8k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.6k
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
まとめ(図)