MENU

phpStan/LaraStan を簡単に比較検証

PHPの静的解析ツールとして、PHPStan なるものがあることを最近知りまして。

こちらの導入により、

PHPを実行しなくても、コード内のエラーを検知してくれる」とのことで、

試してみようかと思いましたが、どうやらLaraStanなるものもあるらしく。

PHPStanの公式を軽く覗いて見たところ、

If you use a popular framework like Symfony, Doctrine or Laravel etc., make sure you install a corresponding extension. It will improve understanding of your code, and also comes with extra rules for correct usage.

参照:https://phpstan.org/user-guide/rule-levels#want-to-go-further%3F

とあり、さらにcorresponding extensionには、

参照:https://phpstan.org/user-guide/extension-library

つまり、

「Laravel を使う場合には、LaraStan(Bladestan)の拡張を入れてね(非公式だけど)」

ということみたいです。

非公式、という点が若干気になりますが、ともかく。
両者を簡単に比較検証して見たいと思います。

PHPStan 公式:URL

LaraStan 公式:URL

目次

検証_前提

動作検証のために、Laravel で簡易的なブログアプリケーションを用意しました。

ブログ記事をCRUDできるだけの最低限なMVCのアプリケーションです。

こちらのRepositoryにPHPStan/LaraStanをそれぞれ導入&比較して見ます。

まずはPHPStanから。

About PHPStan

Install

$ composer require phpstan/phpstan
$ composer install

Usage

詳細な使い方に関しては、以下に記載あり。

公式

基本的な使い方は以下の通り。

$ vendor/bin/phpstan analyse [options] [<paths>...]
  • options(↓ 例)
  • paths
    • 解析対象phpファイル/ディレクトリのpathを指定

↑ これらは、専用の設定ファイルでも設定できるようですが、
今回は用意せず、コマンドだけで実行。

level

実行時に「どの程度をエラーとして検知するか」を示すレベルを「0~9」で指定できるようです。

各レベルの概要を公式から参照すると、以下の通り。(DeepLで翻訳)

0:基本的なチェック、未知のクラス、未知の関数、$this上で呼び出される未知のメソッド、メソッドや関数に渡される引数の数の間違い、常に未定義の変数
1:未定義の可能性のある変数、__call と __get を持つクラスの未知のマジック・メソッドとプロパティ
2:未知のメソッドが ($this だけでなく) すべての式でチェックされる、PHPDocs の検証
3:戻り値の型、プロパティに代入される型
4:基本的なデッドコードのチェック - instanceof やその他の型チェックが常に偽であること。
5:メソッドや関数に渡される引数の型のチェック
6:タイプヒントの欠落を報告
7:部分的に間違ったユニオン型のレポート - ユニオン型の一部の型にのみ存在するメソッドを呼び出した場合、レベル7はそのことをレポートし始めます。
8:NULL 可能な型でのメソッドの呼び出しやプロパティへのアクセスが報告されます。
9:混合型に厳密であること - この型でできる唯一の操作は、この型を別の混合型に渡すことです。
参照: https://phpstan.org/user-guide/rule-levels

level未指定の場合は「0」が設定されるようです。

実際にいくつかレベルを指定して実行した結果を比較してみます。

level:0(最低レベル)

root@8e2c80c182a6:/workspace# vendor/bin/phpstan analyse  app
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
                                                                                                  
 [OK] No errors

エラーは検出されず。

level:5

root@8e2c80c182a6:/workspace# vendor/bin/phpstan analyse --level 5 app
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------------------- 
  Line   Http/Controllers/Api/PostsController.php                      
 ------ -------------------------------------------------------------- 
  21     Call to an undefined static method App\\Models\\Posts::find().  
  30     Call to an undefined static method App\\Models\\Posts::find().  
  41     Call to an undefined static method App\\Models\\Posts::find().  
 ------ -------------------------------------------------------------- 
                                                                                                  
 [ERROR] Found 3 errors

いくつかエラーが検出されました。

levelで言うと、以下に引っかかったものと推測されます。

1:未定義の可能性のある変数、__call と __get を持つクラスの未知のマジック・メソッドとプロパティ

ですが、これらはLaravelの場合、マジックメソッドを利用した正常なアクセスで、アプリケーションも問題なく動作します。

(おそらく、この辺りがLaraStanの場合には正常な処理として認識されると推測)

なので、
この辺りがエラーとして検出されてしまうと、煩わしいです。

level:9(最高レベル)

root@8e2c80c182a6:/workspace# vendor/bin/phpstan analyse --level 9 app
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ----------------------------------------------------------------------------------------- 
  Line   Http/Controllers/Api/PostsController.php                                                 
 ------ ----------------------------------------------------------------------------------------- 
  12     Method App\\Http\\Controllers\\Api\\PostsController::getList() has no return type            
         specified.                                                                               
  16     Call to an undefined method                                                              
         Illuminate\\Contracts\\Routing\\ResponseFactory|Illuminate\\Http\\Response::json().           
  19     Method App\\Http\\Controllers\\Api\\PostsController::getBy() has no return type specified.   
  21     Call to an undefined static method App\\Models\\Posts::find().                             
  23     Call to an undefined method                                                              
         Illuminate\\Contracts\\Routing\\ResponseFactory|Illuminate\\Http\\Response::json().           
  26     Method App\\Http\\Controllers\\Api\\PostsController::save() has no return type specified.    
  30     Call to an undefined static method App\\Models\\Posts::find().                             
  36     Call to an undefined method                                                              
         Illuminate\\Contracts\\Routing\\ResponseFactory|Illuminate\\Http\\Response::json().           
  39     Method App\\Http\\Controllers\\Api\\PostsController::delete() has no return type specified.  
  41     Call to an undefined static method App\\Models\\Posts::find().                             
  47     Call to an undefined method                                                              
         Illuminate\\Contracts\\Routing\\ResponseFactory|Illuminate\\Http\\Response::json().           
 ------ ----------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------- 
  Line   Http/Middleware/RedirectIfAuthenticated.php                                 
 ------ ---------------------------------------------------------------------------- 
  24     Method App\\Http\\Middleware\\RedirectIfAuthenticated::handle() should return  
         Symfony\\Component\\HttpFoundation\\Response but returns                       
         Illuminate\\Http\\RedirectResponse|Illuminate\\Routing\\Redirector.             
 ------ ---------------------------------------------------------------------------- 

 ------ -------------------------------------- 
  Line   Providers/RouteServiceProvider.php    
 ------ -------------------------------------- 
  28     Cannot access property $id on mixed.  
 ------ -------------------------------------- 

                                                                                                  
 [ERROR] Found 13 errors

最高レベルでの解析だと、

型情報の未定義エラーやLaravelが自動生成するクラスに関してもエラーを検出しています。(このエラーも要らんな…)


とりあえずは、ざっとPHPStanの基本的な使い方は抑えられたと思います。

続いて、LaraStanで同様に動作検証してみたいと思います。

About LaraStan

UnInstall

まずは、PHPStanをuninstall

$ composer remove phpstan

Install

LaraStanをInstall

$ composer require larastan/larastan:^2.0 --dev
$ composer install

Setting

LaraStanの場合は設定ファイル(phpstan.neon or phpstan.neon.dist)の使用が推奨されている模様。

公式を元に構成してみます。

  • phpstan.neon
includes:
    - vendor/larastan/larastan/extension.neon
parameters:
    // 検証対象のディレクトリ名を指定
    paths:
        - app/
    // 検証レベルを指定
    level: 5

上記設定ファイルを作成した上で、実際に動作を検証してみます。

Usage

実行コマンドがPHPStanと同じ。

先ほどと同様にいくつかレベルを指定してみて、動作検証。

level:0(最低レベル)

// 検証レベルは設定ファイルで指定しているので、optionsは未指定です
root@8e2c80c182a6:/workspace# ./vendor/bin/phpstan analyse
Note: Using configuration file /workspace/phpstan.neon.
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
                                                                                    
 [OK] No errors

PHPStanと同様にエラーは出ませんでした。

level:5

root@8e2c80c182a6:/workspace# ./vendor/bin/phpstan analyse
Note: Using configuration file /workspace/phpstan.neon.
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------------------------------- 
  Line   Http/Controllers/Api/PostsController.php                                  
 ------ -------------------------------------------------------------------------- 
  32     Access to an undefined property                                           
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int,             
         App\\Models\\Posts>::$title.                                                
         💡 Learn more:                                                            
            <https://phpstan.org/blog/solving-phpstan-access-to-undefined-property>  
  33     Access to an undefined property                                           
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int,             
         App\\Models\\Posts>::$content.                                              
         💡 Learn more:                                                            
            <https://phpstan.org/blog/solving-phpstan-access-to-undefined-property>  
 ------ -------------------------------------------------------------------------- 

                                                                                                  
 [ERROR] Found 2 errors

PHPStanの場合に出力されていた、以下エラーが無くなりました。

Line Http/Controllers/Api/PostsController.php
21 Call to an undefined static method App\Models\Posts::find().

(マジックメソッドによる正常なアクセスであることを検知してくれている模様)

ですが、新しく以下エラーが検知されました。

Line Http/Controllers/Api/PostsController.php
32 Access to an undefined property
App\Models\Posts|Illuminate\Database\Eloquent\Collection<int,
App\Models\Posts>::$title.

エラーの内容的には、

「PostsModelに定義されていない”title”プロパティにアクセスされました」というものですが、

Eloquent/Postsクラスにphpdocによる@propety titleの記載が無いことが原因で発生したものですね。

(これは何でPHPStanでは検知されないのだろうか…?)

愚直に@propetyを書くか、laravel-ide-helper を導入すれば解決しそうです。

level:9(最高レベル)

root@8e2c80c182a6:/workspace# ./vendor/bin/phpstan analyse
Note: Using configuration file /workspace/phpstan.neon.
 21/21 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ----------------------------------------------------------------------------------------- 
  Line   Http/Controllers/Api/PostsController.php                                                 
 ------ ----------------------------------------------------------------------------------------- 
  12     Method App\\Http\\Controllers\\Api\\PostsController::getList() has no return type            
         specified.                                                                               
  19     Method App\\Http\\Controllers\\Api\\PostsController::getBy() has no return type specified.   
  23     Cannot call method toArray() on                                                          
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int, App\\Models\\Posts>|null.    
  26     Method App\\Http\\Controllers\\Api\\PostsController::save() has no return type specified.    
  32     Cannot access property $title on                                                         
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int, App\\Models\\Posts>|null.    
  33     Cannot access property $content on                                                       
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int, App\\Models\\Posts>|null.    
  34     Cannot call method save() on                                                             
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int, App\\Models\\Posts>|null.    
  39     Method App\\Http\\Controllers\\Api\\PostsController::delete() has no return type specified.  
  46     Call to an undefined method                                                              
         App\\Models\\Posts|Illuminate\\Database\\Eloquent\\Collection<int,                            
         App\\Models\\Posts>::delete().                                                             
 ------ ----------------------------------------------------------------------------------------- 

                                                                                                  
 [ERROR] Found 9 errors

PHPStanでは出力されていた、以下のようなLaracelがデフォルトで用意するクラスで検知しているエラーが消えて、自作クラスのエラーだけ検知している!!

Line Http/Middleware/RedirectIfAuthenticated.php

24 Method App\Http\Middleware\RedirectIfAuthenticated::handle() should return
Symfony\Component\HttpFoundation\Response but returns
Illuminate\Http\RedirectResponse|Illuminate\Routing\Redirector.

で、新規でいくつかエラーが検知。

23 Cannot call method toArray() on
App\Models\Posts|Illuminate\Database\Eloquent\Collection<int, App\Models\Posts>|null.

これは、以下のエラーに引っかかっている模様。

「7:部分的に間違ったユニオン型のレポート – ユニオン型の一部の型にのみ存在するメソッドを呼び出した場合、レベル7はそのことをレポートし始めます。」

なるほどー!!!

確かに。これは直した方が良いかー。

まとめ

Laravel のApplicationなら、LaraStanを使う方が良さそう(当たり前か)

以下、理由。


  • PHPStanでは、Laravel Applicationなら本来想定されている使い方であるにも関わらず、エラーとして検知するケースがあるので、煩わしい。
    • 例)
      • Eloquentのマジックメソッド
      • Laravelがデフォルトで用意するクラス内
  • 一方で、LaraStanでだけ検知されるエラーがあったが、内容的に検知されるべきエラーではあった。
    • PHPStanでも検知されるべきだと思うのだけど、何で検知されないのだろう、と内容のエラーだった(理解が浅いだけだが)

→ LaraStanの方が生産的!!!と感じました。

Reference

level=0 から始める PHPStan(Larastan) 導入ガイドhttps://blog.shin1x1.com/entry/getting-stated-with-phpstan

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次