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
3k
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
1
49
PHPのアノテーション(アトリビュート)からOpenAPIのドキュメントを出力し、レスポンスもそれを元にシリアライズすることで仕様と実装を乖離させず、色々楽できたよって話
kalibora
0
130
Symfony2 の Functional Test のメモリ使用量と実行時間を削減した話
kalibora
0
5
WebAudioと音の話
kalibora
0
390
Other Decks in Programming
See All in Programming
useSyncExternalStoreを使いまくる
ssssota
6
1.2k
どうして手を動かすよりもチーム内のコードレビューを優先するべきなのか
okashoi
3
190
Stackless и stackful? Корутины и асинхронность в Go
lamodatech
0
830
テスト自動化失敗から再挑戦しチームにオーナーシップを委譲した話/STAC2024 macho
ma_cho29
1
1.3k
CQRS+ES の力を使って効果を感じる / Feel the effects of using the power of CQRS+ES
seike460
PRO
0
140
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
500
Kaigi on Railsに初参加したら、その日にLT登壇が決定した件について
tama50505
0
100
ChatGPT とつくる PHP で OS 実装
memory1994
PRO
2
110
クリエイティブコーディングとRuby学習 / Creative Coding and Learning Ruby
chobishiba
0
3.9k
LLM Supervised Fine-tuningの理論と実践
datanalyticslabo
7
1.3k
情報漏洩させないための設計
kubotak
3
340
Androidアプリのモジュール分割における:x:commonを考える
okuzawats
1
140
Featured
See All Featured
Fireside Chat
paigeccino
34
3.1k
Java REST API Framework Comparison - PWX 2021
mraible
28
8.3k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
The World Runs on Bad Software
bkeepers
PRO
65
11k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
29
2k
Raft: Consensus for Rubyists
vanstee
137
6.7k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
95
17k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
28
900
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
Optimising Largest Contentful Paint
csswizardry
33
3k
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
まとめ(図)