Upgrade to Pro — share decks privately, control downloads, hide ads and more …

モダン PHP テクニック 12 選 ―PsalmとPHP 8.1で今はこんなこともできる!―

sji
December 21, 2021

モダン PHP テクニック 12 選 ―PsalmとPHP 8.1で今はこんなこともできる!―

2021/12/21 PHPerKaigi petit - PHP8.1リリース祝賀会 でのトーク「モダンPHPテクニック 12選 ―PsalmとPHP 8.1で今はこんなこともできる!―」のスライドです。発表時点からごくわずかに加筆修正した部分があります。

https://phperkaigi.connpass.com/event/233022/

sji

December 21, 2021
Tweet

More Decks by sji

Other Decks in Programming

Transcript

  1. JetBrains の調査いわく 「どんな品質ツールを使っていますか」 PHPStan Psalm Phan 計 2019 9% 1%

    5% 15% 2020 11% 3% 2% 16% 2021 18% 9% 2% 29% 「静的解析ツールを使ってますか」 はい いいえ なにそれ 2021 33% 38% 28% https://www.jetbrains.com/lp/devecosystem-2019/php/ https://www.jetbrains.com/lp/devecosystem-2020/php/ https://www.jetbrains.com/lp/devecosystem-2021/php/
  2. 1. クラスをボコボコ生やす PHP でデータや処理に型をつける 最強の手段 言語機能やツールの恩恵を最大限に 得られる Constructor Property Promotion

    最高 ちょっとした処理や値の表現でもク ラスを定義 class SomeThing { public function __construct( public int $id, public string $name, public Dependency $obj, ) { } }
  3. 2. DI コンテナで autowiring 多くの DI コンテナで利用可能 Laravel の Container

    や PHP-DI など コンストラクタへ依存クラスを並べ ていくだけ 自分で new するのは VO とかテスト とかごく一部に Laravel でも Facade より DI // DI コンテナが勝手に依存先を生成 public function __construct( public readonly Dependency1 $obj1 public readonly Dependency2 $obj2 ) { }
  4. 3. 型は早めにつける @var より @param どんどん呼び出し元へ追いやる 結果コントローラなどリクエス トの入口のほうに 誤りは早く検知できるほどよい リリースの後より前

    テスト環境より CI CI より手元のエディタ リクエスト処理の後半より前半 // こっちより public function f(array $items) { /** @var Item $item */ foreach ($items as $item) { } } // こっち /** @param Item[] $items */ public function f(array $items) { foreach ($items as $item) { } }
  5. 4. 静的解析可能な入力値検査 一部ライブラリで静的型検査フレ ンドリーに入力値の検査やキャス トが可能 webmozart/assert azjezz/psl cuyz/valinor OpenAPI や

    JSON scheme など IDL からのクラス自動生成も有効 // webmozart/assert の例 Assert::integer($id); // $id はこの先 int 扱い // azjezz/psl の例 $spec = Type\shape([ 'id' => Type\int(), 'name' => Type\string(), 'age' => Type\optional( Type\int() ) ]); // $input をバリデーションしつつキャスト $input = $spec->coerce($_POST['input'] // $input はこの先で ↓ の扱い // array{id: int, name: string, age?:
  6. 5. 静的解析可能な config 連想配列のかわりに ValueObject を 使う DI コンテナに登録して取り出すの もよい

    型の保護や補完が効く 名前付き引数を利用 順番の意識が不要に 設定項目の追加や削除なども静 的に検知可能 return new DatabaseConfiguration( driver: new MySQLDriverConfiguration host: 'localhost', port: 3306, db_name: 'test_db', username: 'test', password: 'mogera', ), )
  7. 6. 静的解析可能な Collection を使う 最近 illuminate/collections がジェネ リクスに対応 doctrine/collections も以前からジェ

    ネリクスに対応 PhpStorm もそれらのジェネリクス にある程度対応 あかん場合は @var と @psalm- ignore-var を併用 /** @param Collection<User> $users */ public function f(Collection $users) foreach ($users as $user) { // $user は User として型推論 } }
  8. 7. 静的解析可能な Orm を使う doctrine/orm cycle/orm も 2 系以降でいくらか eloquent

    はlarave-plugin やアノテー ション生成が必要 Orm を自作してでも静的解析に対 応させる cuyz/valinor など既存 hydrator の利用も可 // $user は User|null と推論される $user = $entityManager ->getRepository(User::class) ->find(1);
  9. 8. 静的解析可能な配列を使う psalm の型アノテーションでは配 列のキーや値の型が指定可能 「連番の数値添字が0 から順に並ん だ配列」(list )も指定可能 non-empty-array

    や non-empty-list もある Shapes で構造体的な配列やタプル も宣言できる /** @param array<ItemType> $p */ /** @param array<int, ItemType> $p */ /** @param list<ItemType> $p */ /** @param non-empty-array<ItemType> $p /** * @param array{ * id: int, * name: string * } $p */ /** @param array{int, string} */
  10. 9. 静的解析可能な分岐網羅を使う PHP 8.0 で入った match は分岐網羅 検査がある 想定してない値が来ると例外 Psalm

    は default のない match や switch で静的に分岐網羅を検査でき る場合がある 値の Union や Enum をうまく使う 無闇に可変関数呼び出しなどの動的 処理は使わない、愚直に分岐を書く enum Result { case Succeed; case Failed; } // ResultStatus::Failed を網羅してないの // 静的解析段階でエラーになる function f(Result $status): int { return match ($status) { ResultStatus::Succeed => 1, }; }
  11. 10. 書き込み、状態を減らす PHP 8.1 で readonly が追加 Psalm でも @immutable

    や @pure などがある なるべく完全コンストラクタ+不変 状態を持たない = 各生成時点で全情 報が必要 小さなクラスが増え神クラス化 も防ぎやすくなる class ReadOnlyClass { public function __construct( public readonly int $id, public readonly string $name, ) { } } /** @psalm-immutable */ class ImmutableClass { public function __construct( public int $id, public string $name, ) { } }
  12. 11. なるべく多くを型で表現 ValueObject 、DTO みたいなのをバ ンバン作る ID の種類ごとに異なるクラスを定 義する ジェネリクスを使ってもよい

    実クラスを定義しなくとも型 タグを使える ちかぢか同僚が会社のブロ グで紹介するかも trait ItemId { public function __construct( public readonly int $value, ) { } } class ConsumableItemId {use ItemId;} class EquipmentItemId {use ItemId;} class Consumableuser { public function useItem( ConsumableItemId $item_id ): void { } }
  13. 12. Package-By-Feature (に寄せる) UserController UserModel UserView ItemController ItemModel ItemView CurrencyController

    CurrencyModel CurrencyView User Item Currency Package By Feature UserModel ItemModel CurrencyModel UserController ItemController CurrencyController UserView ItemView CurrencyView Model Controller View Package By Layer PBL (Package-By-Layer) へ寄せすぎない 〜Controller だけのディレクトリ 〜Repository だけのディレクトリ 何が嬉しいのか @psalm-internal がうまく機能するよう書く