BookmarkLINEPHPでWEBアプリケーション開発を行う際の静的コード解析ツールと言えばPHPStanが有名ですが、それをLaravel用に最適化したLarastanというパッケージがあります。今回は、Larastanを使ってLaravelアプリケーションの実装コードの静的解析を行っていきます。今回の開発環境は以下の通りです。1.8Laravelのルートディレクトリを「laravel/」としています。プログラム解析」とも呼ばれています。前回の記事(LaravelにPHP_CodeSnifferを導入しコーディング規約(PSR)に沿った記述を行う)で紹介した構文チェックも、広義にはこれに含まれます。LarastanLarastanは、Laravel用のPHPStanラッパーです。https://github.com/nunomaduro/larastan2018年11月にリリースされた比較的新しいパッケージになります。利用するには以下のシステム要件を満たしている必要があります。以上LaravelにPHPStanを導入する場合、Laravelの仕様上インストールしただけではスムーズに使えず、マジックメソッドなどフレームワークの仕様部分を一部フォローしてあげる(PHPStanに適切に理解させる)必要があります(2019年8月現在)。LarastanはPHPStanをラップし、この部分を解決させたパッケージになります。FW仕様のフォローに時間をかけない事、フォローの為のパッケージや設定ファイルを増やさない事を考えると選択肢の1つとしておすすめです。インストールcomposerにて導入します。以下のコマンドを叩いてインストールします。nunomaduro/larastan静的コード解析実行まだ何も実装を行ってはいませんが、動作確認を兼ねて静的コード解析を実行してみます。appとtestsディレクトリを対象に、レベルはMAX(最高値=7=最も厳しい)で解析を行います。実行には以下のartisanコマンドを叩きます。エラーなく実行できる事が確認できました。ちなみにLarastanは、デフォルト(オプション未指定)で実行した場合は以下の動作をします。解析対象(ファイルやディレクトリ)未指定の場合appディレクトリのみを見に行くレベル未指定の場合レベル5で実行レベルは0~7まであり、7が最大値(厳しい)となっています。phpstan.neonartisanコマンドにオプションである解析対象のパスとレベルを付けて実行しましたが、LarastanもPHPStanと同じように、phpstan.neonへオプションを書き出し、コマンドを簡単にすることができます。includeする点です。phpstan.neonを作成する場合はこれを行わないとLarastanの設定が読み込まれないので恩恵に預かる事ができません。Larastanのextension.neonでは「paths」が効きません。上記で設定した「app」と「tests」は無視されます。結局artisanコマンドでpathsオプションは付けてあげないとappディレクトリのみを解析してしまいます。これはバグとして認識されているのですが、2019年8月現在、まだ修正されていません。nunomaduro/larastan修正されるまではcomposer.jsonのscriptsに登録して使うのが良いかもしれません。]}実装コードを解析する1ここからは実際にコーディングを行い、静的コード解析を行っていきます。}}上記は足し算を行う処理ですが、このコードは問題無いのでテストに通ります。ここから、一方の値を文字列型にしてみます。$b);}この状態で静的解析を実行すると、エラーとなります。足し算を行うaddition()メソッドの引数には数値型を指定しているので、静的コード解析で文字列型が渡ってくる事が判明した為、エラーとなりました。この例は実装もエラー箇所も単純なのでなかなか解析の有り難みを受けにくいかもしれませんが、最大の恩恵は処理を実行せずにこうした不整合を発見できた事にあります。処理がもっともっと入り組んで複雑な機能などの場合では知らず知らずのうちに意図しない形式のデータを渡してしまう事もありますし、チームで開発していて自分以外のメンバーが実装した処理の仕様に気づかない場合もあります。これらは間々ある事ですが、それを、コードを実行せずに判定出来るという事がこの静的コード解析の最大の利点です。実装コードを解析する2もう一つ例を見てみます。今度はEloquentを絡めてデータを取得します。}}上記はユーザーのデータをデータソースから取得し、それを何かしらの処理を行ってから返却するまでの一連の処理になっています。この実装の場合、静的解析を行うとエラー無く通ります。$this->userOperation($user);}この場合はエラーになります。前者はfirst()で取得しているので単数での取得となりUserオブジェクトが返却されるのに対して、後者はget()で取得しているのでCollectionオブジェクトで返却されます。よって、次のuserOperation()に渡す際の引数のタイプヒンティングで不整合が起きる為にエラーとなってしまいます。取得出来るデータはどちらでも目的通りのものになりますが、少しの違いでこうして意図しないものを次へ渡してしまう事が結構カジュアルに起こるものです。これは実行すればエラーとなりスタックトレースが表示されますが、わざわざブラウザを叩かなくても静的解析を走らせれば処理を実行しなくてもミスに気づく事ができます。実装コードを解析する3ちなみに、データソースから取得するデータは必ずしも期待した結果(同一の型)ではない事もあります。100)->first();Eloquentのfirst()メソッドでの取得の場合は、データが存在する場合はUserオブジェクトが返りますが、データが存在しない場合はnullが返ってきます。ちなみにこのメソッドのPHPDocには、返り値が以下の様に記述されています。$this->take(1)->get($columns)->first();}これでは次の、引数をUserオブジェクトとしてタイプヒンティングしているuserOperation()メソッドには値を渡す事ができません。PHPStanでは実行ファイルを動作させるわけではないのでメソッドの返り値などをPHPDocから読み取ったりしながら解析を行います。nullが返ってくる可能性があると判断するので、静的解析を走らせるとエラーとなります。--------------------------------------------------------------------------------------------------------------------「Userオブジェクトの型指定してるけど、これ実際にはmixedな型で入ってきますよ。」しかしながらこれは正常な判定です。この実装の場合、必ずユーザーデータが返ってくる(し、返ってくるであろう検索しか行わない)と暗黙的に了解し実装してしまっているので、それに伴って次のuserOperation()メソッドでも、Userオブジェクトの型指定を行ってしまっている事に気付かされます。実際は、nullである場合の状況もハンドリングしてあげるべきなのです。いう事で、しっかり処理を挟みます。$this->userOperation($user);}「必ずユーザーデータが返ってくる」という前提なので、今回はnullなら例外を投げています。いう処理を行えばスムーズ且つクリーンなのかは状況によりますが、こうして然るべき状態をハンドリングしてあげることで次の処理に想定外の値(型)が渡される事が無くなるので、堅牢な実装を継続する事ができます。今回の場合、もし型指定を外してもnull値が渡ったらエラーになるでしょう。「必ずユーザーデータが返ってくる」前提で実装されているので、そうでなかった時の処理はおそらく書かずにリリースされる可能性もあります。気づく事が出来たからこそ予めハンドリングを行えて、異常終了しない処理を実装できそうです。何よりこの実装なら、静的コード解析を無事に通過します。errorsエラーを無視するにっちもさっちもいかなくなったら、エラーを無視するという手段も取れます。その場合は設定ファイルにignoreErrorsを定義します。given#'正規表現も使えます。上記のコメントアウトされている記述は、変数名を正規表現で置き換えた場合です。これでも適用されます。これは、FWの性格上どうしても回避せざるを得ない場合とかに限り使用するのが良いと思います。動作を理解した上で無視しないと、本来エラー検知しなくてはならないところを通過させてしまう可能性があるからです。なんでもかんでも除外していると、気がついたら結構な量のエラー無視になり、管理しきれなくなります。これでは、静的コード解析を行う意味がありません。まとめ以上で作業は終了です。静的コード解析を行う事で実装中の気づきは沢山得られると思います。開発はトライ&エラーの繰り返しですが、大切な事は走らせたアプリケーションを予想外の振る舞いで止めない事。未知の脅威の排除はできるだけしておきたいところです。その為にこういった解析ツールも前向きに導入していきたいですね。BookmarkLINELaravelにPHP_CodeSnifferを導入しコーディング規約(PSR)に沿った記述を行うLaravelでBasic認証~全体/一部にアプリケーションから側から気軽に設定する~LaravelでRedisを使う入門編。SESを使ってAPIメール送信を行う(Mailableクラス&SimpleEmailService)2019.12.3MySQL8.0.16で追加されたCHECK制約を試してみる。RitoLaboこのサイトについて利用規約プライバシーポリシ。