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
170
PHPのアノテーション(アトリビュート)からOpenAPIのドキュメントを出力し、レスポンスもそれを元にシリアライズすることで仕様と実装を乖離させず、色々楽できたよって話
kalibora
0
150
Symfony2 の Functional Test のメモリ使用量と実行時間を削減した話
kalibora
0
5
WebAudioと音の話
kalibora
0
400
Other Decks in Programming
See All in Programming
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
800
プログラミング言語学習のススメ / why-do-i-learn-programming-language
yashi8484
0
130
pylint custom ruleで始めるレビュー自動化
shogoujiie
0
120
1年目の私に伝えたい!テストコードを怖がらなくなるためのヒント/Tips for not being afraid of test code
push_gawa
0
220
苦しいTiDBへの移行を乗り越えて快適な運用を目指す
leveragestech
0
650
CSS Linter による Baseline サポートの仕組み
ryo_manba
1
110
『品質』という言葉が嫌いな理由
korimu
0
160
Honoをフロントエンドで使う 3つのやり方
yusukebe
7
3.3k
Formの複雑さに立ち向かう
bmthd
1
860
CI改善もDatadogとともに
taumu
0
120
楽しく向き合う例外対応
okutsu
0
150
Boost Performance and Developer Productivity with Jakarta EE 11
ivargrimstad
0
360
Featured
See All Featured
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
Writing Fast Ruby
sferik
628
61k
Being A Developer After 40
akosma
89
590k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
12
960
Side Projects
sachag
452
42k
Building an army of robots
kneath
303
45k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
27
1.9k
Scaling GitHub
holman
459
140k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
120k
It's Worth the Effort
3n
184
28k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
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
まとめ(図)