LoginSignup
2
1

More than 1 year has passed since last update.

[PHP/Laravel]やろうぜ、静的解析

Posted at

はじめに

きっかけ

この話題を話そうと思ったきっかけは主に2点

想定読者

  • 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の自動化を最終ゴールにした記事なので参考になります。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1