はじめに
きっかけ
この話題を話そうと思ったきっかけは主に2点
- 主催している勉強会で話題になった。
- 意外と導入している企業少ない?
- 結構PHP Conferenseで定期的に話題になってる
想定読者
- PHPで既存のコードによるバグに悩んでいる人
- これからプロジェクトの基盤を作る人(僕はこれだった)
静的解析とは?
実行ファイルを実行することなく解析を行うこと
エラーとかバグがないかを検証
要は...
ポチポチテストせずにバグの温床潰しこめてハッピー!ってこと
PHPstanとは?
- 多分PHPで一番有名でスター数も圧倒的な静的解析ライブラリ
- PHP Conferenseでも大体これが取り上げられる
- JavaScriptで行くとeslintの立ち位置
- 検出する静的解析エラーのレベルを0~9で指定できるので、段階的に導入も可能
- どうしようもないやつはignoreできたり、phpstanが用意している検証ルールを無効にしたりと柔軟性〇
導入の必要性
以下メリットあり
【運用効率向上】想定外バグを生むコードをマージしないようにできる
よくわからないまま使ったメソッドが予想外の戻り値を返す
↓
原因不明のバグが発生
↓
対応に追われる
...みたいなことが無くなる
【開発効率向上】PHP DOC見ただけでメソッドの使い方がパッとわかる
- level4以上だとPHPDocで@returnや@paramを書かないとエラーになる
- 必ずメソッドやプロパティにPHPdocが書かれるようになり、ぱっと見でそのメソッドやプロパティの内容がわかるようになる
使い方
散々PHPstan, PHPstanいうてましたが、今回のデモはラッパーライブラリのLarastanを使います。
とはいえ、大本はPHPStanなのでやれることは同じです。
インストール
Docの通りcomposer installすればOK
※最新版インストールにはPHP 8系以上+Laravel 9系以上(まじかよ)が必要なので、ここはバージョン指定が必要。。。なので、以下でOKだと
composer require nunomaduro/larastan:^1.0 --dev
いろんなカスタマイズ
まずは設定ファイルの作成
phpstan.neon
か phpstan.neon.dist
をプロジェクト直下に作成(以下はDocのまま)
※コメントで各項目の説明あり
includes: // 1, larastanの取り込み. ≒requireみたいなもの
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths: // larastanで解析する対象範囲決め. ここではappは以下のファイルを全て対象
- app
# The level 9 is the highest level
level: 5 // 先ほどお話したレベル。詳細は https://phpstan.org/user-guide/rule-levels
ignoreErrors: // どうしてもはじきたい、どうにもならないエラーを登録(※)
- '#PHPDoc tag @var#'
excludePaths: // 除外したいファイル「appのこのファイルだけは除外したい」みたいな
- ./*/*/FileToBeExcluded.php
// こっからは詳細ルール. 詳細は以下
// Doc: https://github.com/nunomaduro/larastan/blob/master/docs/rules.md
// Qiita: https://qiita.com/MasaKu/items/7ed6636a57fae12231e0#larastan%E3%81%AE%E8%A7%A3%E6%9E%90%E3%83%AB%E3%83%BC%E3%83%AB
// ちなみに以下はPHPのタイプヒントの欠落の検出を無効にするエラー
// https://phpstan.org/config-reference#vague-typehints
checkMissingIterableValueType: false
※ignoreはソースコード側で以下でもできる(Docにも記載有)
// @phpstan-ignore-next-line
$test->badMethod();
もっと詳細知りたいならLarastanのDocかPHPstanのDocへGo
実行
./vendor/bin/phpstan analyse
使用例
未定義の変数呼び出し、未定義関数・クラスの呼び出し、インスタンス化(ザ・致命的なエラー)
- レベル…0
↓実際にやってみる
------ --------------------------------------
Line Http\Controllers\HelloController.php
------ --------------------------------------
27 Undefined variable: $hoge
------ --------------------------------------
実際はほぼ通らないけどバグの温床になりうるコードを検出できる
if (false) {
echo $hoge; // $hogeなんて変数定義してない!
}
常にtrueを返す無駄な条件文検出
- レベル…4
↓実際にやってみる
------ -----------------------------------------------------------------------
Line Http\Controllers\HelloController.php
------ -----------------------------------------------------------------------
28 If condition is always true.
無意味な条件分岐は消しましょう、ということ
$true = true;
if ($true) { // 100%trueになる条件文...
echo $hoge;
}
実引数、戻り値の指定し忘れ
- レベル…6
↓実際にやってみる
------ -----------------------------------------------------------------------------------
Line Http\Controllers\HelloController.php
------ -----------------------------------------------------------------------------------
19 Method App\Http\Controllers\HelloController::index() has no return type
specified.
チャンと引数、戻り値を指定しましょう
/**
* index.
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
{
return view('hello.index', $data);
}
nullプロパティへのアクセス検出
- レベル…8 (僕はこれで実務。最初はPHPStanの回避が結構キツかった(笑))
↓実際にやってみる
------ -------------------------------------------------------------------------------------------------------------------
Line HelloController.php
------ -------------------------------------------------------------------------------------------------------------------
29 Cannot access property $userAttribute on App\Models\User|null.
Laravelの組み込み関数のfirst()はDBからデータが取得できない場合はnullを返すので注意
※完全余談ですが、元をたどるとヘルパー関数のArr::first呼び出してnullを返してるんですけどね
だからabortするとか
$user = User::first();
if (is_null($user)) {
abort(404);
}
abortだとコード汚くなるのでデータないときは404返すfirstOrFail()使うか
$user = User::firstOrFail();
プロパティやメソッドにアクセス時にnull安全オペレータ使うとか
$user?->name;
$user?->fill(...);
で対応。
mixed型の禁止
レベル...9
➡かなりキツイ。。。Laravelの組み込み関数などで戻り値によく使われているので大量のエラーの想定
※やったことないので割愛
最後に
個人的にはエラーが起きた時にPHP、Laravelの組み込みメソッドをしっかり見るようになるので、コードリーディングになってすごく勉強になりました。
CIへの導入が最終到達点だと思います。
以下がCIの自動化を最終ゴールにした記事なので参考になります。