この記事で書くこと
- 新規開発案件にLarastan(PHPStanのLaravel用拡張)を導入し Level0 から Level5 まで一つずつ上げ、どのようなエラーが出て、どのようにエラー解消したかのまとめ
この記事で書かないこと
- PHPStan, Larastanのインストール方法や使い方
- 静的解析初心者なので、効率的な対応方法などをお探しの方のご期待には沿えないかと思います
Level 0
実行コマンド
./vendor/bin/phpstan analyse --level 0 > phpstan.txt
実行結果(抜粋)
[ERROR] Found 97 errors
------ -----------------------------------------------------------------------------------
Line Http/Controllers/Hoge/AccountController.php
------ -----------------------------------------------------------------------------------
93 Method App\Http\Controllers\Hoge\AccountController::show() should return Illuminate\Http\Response but return
statement is missing.
147 Method App\Http\Controllers\Hoge\AccountController::destroy() should return Illuminate\Http\Response but return
statement is missing.
------ -----------------------------------------------------------------------------------
97のエラーのうちほとんどがこれ。
@returnの記述がPHPDocにあるが return が無いのでエラー。
対応内容
ResourceControllerをmakeコマンドで自動生成して以下のような使っていない関数がそのまま放置されていたので削除。
/**
* Display the specified resource.
*
* @param \App\Models\Account $account
* @return \Illuminate\Http\Response
*/
public function show(Account $account)
{
//
}
実行結果(抜粋)
------ ---------------------------------------------------------------------
Line Http/Controllers/Auth/RegisterController.php
------ ---------------------------------------------------------------------
67 Call to static method create() on an unknown class App\Models\User.
💡 Learn more at https://phpstan.org/user-guide/discovering-symbols
------ ---------------------------------------------------------------------
存在しないクラス App\Models\User の静的メソッド create() を呼び出していてエラー。
対応内容
Auth入れると自動で生成されるControllerなのでいったんコメントアウト
実行結果(抜粋)
------ --------------------------------------------------------------------------
Line Services/Hoge/HogeService.php
------ --------------------------------------------------------------------------
23 Call to static method error() on an unknown class App\Services\Hoge\Log.
💡 Learn more at https://phpstan.org/user-guide/discovering-symbols
24 Instantiated class App\Services\Hoge\DbErrorException not found.
💡 Learn more at https://phpstan.org/user-guide/discovering-symbols
------ --------------------------------------------------------------------------
不明なクラス App\Services\Hoge\Log の静的メソッド error() を呼び出している。
インスタンス化されたクラス App\Services\Hoge\DbErrorException が見つかりません。
というエラー。
対応内容
use Illuminate\Support\Facades\Log;
use App\Exceptions\DbErrorException;
をnamespaceに書き忘れていたので追加。
Level 1
実行コマンド
./vendor/bin/phpstan analyse --level 1 > phpstan.txt
実行結果(抜粋)
[ERROR] Found 3 errors
------ -----------------------------------------------------------------------------
Line Http/Controllers/Hoge/HogeController.php
------ -----------------------------------------------------------------------------
60 Call to function compact() contains possibly undefined variable $activitys.
------ -----------------------------------------------------------------------------
]
関数 Compact() の呼び出しには、未定義の変数 $activitiys が含まれている可能性があります。
対応内容
if ($account) {
$activitys = $this->service->getActivitys($account_id);
} else {
+ $activitys = "";
}
if ($account)
が else のときに $activities が未定義でcompact() に渡される…
else 時の処理が間違っていたので修正。
Level 2
実行コマンド
./vendor/bin/phpstan analyse --level 2 > phpstan.txt
実行結果(抜粋)
[ERROR] Found 165 errors
------ --------------------------------------------------------------------------------------
Line Http/Controllers/Hoge/AccountController.php
------ --------------------------------------------------------------------------------------
94 Access to an undefined property App\Models\Account::$authority.
💡 Learn more: https://phpstan.org/blog/solving-phpstan-access-to-undefined-property
------ --------------------------------------------------------------------------------------
未定義のプロパティ App\Models\Account::$authority へのアクセス。
165エラーのうちほとんどがこれ。
該当箇所は以下。
Modelのプロパティがマジックメソッドなので発生している。
if (auth()->user()->authority != 1)
対応内容
本来、Larastanはmigrationファイルを見て判断してくれるが、migrationファイルのない案件だったので
phpstan.neon ファイルに以下記述。
ignoreErrors:
# Modelのプロパティがマジックメソッドなので発生するエラー。migrationファイルが無くエラー回避できないので無視する
- '#Access to an undefined property App\\Models(.*)#'
- '#Access to an undefined property object#'
Level 3
実行コマンド
./vendor/bin/phpstan analyse --level 3 > phpstan.txt
実行結果(抜粋)
[ERROR] Found 2 errors
------ -------------------------------------------------------------------------------------------------------------------------------------------
Line Http/Controllers/Hoge/HogeController.php
------ -------------------------------------------------------------------------------------------------------------------------------------------
26 Method App\Http\Controllers\Hoge\HogeController::index() should return
Illuminate\Http\Response but returns Illuminate\View\View.
------ -------------------------------------------------------------------------------------------------------------------------------------------
メソッド App\Http\Controllers\Hoge\HogeController::index() は Illuminate\Http\Response を返す必要がありますが、Illuminate\View\View を返しています。
対応内容
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\View\View
*/
public function index()
{
$records = $this->service->getRecords();
return view(self::PAGE . '.index', compact('records'));
}
@return の型指定が間違っていたので修正。
Level 4
実行コマンド
./vendor/bin/phpstan analyse --level 4 > phpstan.txt
実行結果(抜粋)
[ERROR] Found 3 errors
------ --------------------------------------------------------------------------------------------------------------------------------------------
Line Rules/HogeExtention.php
------ --------------------------------------------------------------------------------------------------------------------------------------------
30 Else branch is unreachable because previous condition is always true.
💡 Because the type is coming from a PHPDoc, you can turn off this check by
setting treatPhpDocTypesAsCertain: false in your phpstan.neon.
------ --------------------------------------------------------------------------------------------------------------------------------------------
前の条件が常に true であるため、Else ブランチに到達できません。
対応内容
class HogeExtention implements InvokableRule
{
/**
* Run the validation rule.
*
* @param string $attribute
- * @param array $value
+ * @param array|\Illuminate\Http\UploadedFile $value
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
*/
public function __invoke($attribute, $value, $fail)
{
if (is_array($value)) {
// 省略
else {
//ここに到達できないよというエラー
}
}
@paramの型指定が間違っていたので修正。
実行結果(抜粋)
------ ----------------------------------------------------------------------------------
Line Services/Hoge/HogeService.php
------ ----------------------------------------------------------------------------------
12 Property App\Services\Hoge\HogeService::$model is never read, only written.
💡 See: https://phpstan.org/developing-extensions/always-read-written-properties
------ ----------------------------------------------------------------------------------
プロパティ App\Services\Hoge\HogeService::$model は読み取られることはなく、書き込まれるだけです。
対応内容
class HogeService extends Service
{
public function __construct(
private Activity $model
) {
}
public function getActivitys($id)
{
- $activity = Activity::find($id);
+ $activity = $this->model::find($id);
return $activity;
}
}
プロパティを宣言していたが、間違えてModelを使っていたので修正。
Level 5
実行コマンド
./vendor/bin/phpstan analyse --level 5 > phpstan.txt
実行結果(抜粋)
[OK] No errors
レベル5までエラーが出なくなりました
ちなみにLevel 6 に上げると
[ERROR] Found 326 errors
------ --------------------------------------------------------------------------------------------------
Line Http/Controllers/Admin/AccountController.php
------ --------------------------------------------------------------------------------------------------
42 Method App\Http\Controllers\Hoge\HogeController::storeInput() has no return type specified.
------ --------------------------------------------------------------------------------------------------
メソッドに戻り値の型が指定されていませんエラー。
326エラーのほとんどがこれだったので断念
level max まで辿りつけるように精進します...
感想
初めて、静的解析を使ってみましたが、なぜもっと早く使わなかったのかと思いました。
宣言したのに使っていないプロパティとか、namespaceの間違いなど、バグに繋がるところを指摘してもらえるのでとても有り難い。
ということで、全社的に本番環境にpushする前に必ず静的解析かけようという決まりができました。
level6以上を達成するには社内のコーディングルールの見直しが必要なのですぐには難しそうですが、少しずつリファクタリングして上げていきたいです。