Blogical

AWS/Salesforceを中心に様々な情報を配信していきます(/・ω・)/

FuelPHPにPHPStanを導入し静的解析をする際の拡張元クラスの読み込みの設定

こんにちは。福島です。今回はFuelPHPというPHPのwebフレームワークを使用したプロジェクトに、PHPStanという静的解析ツールを導入した時、解析対象のクラスの拡張元クラスの読み込みがうまくいかず困りましたので記事にしたいと思います。

静的解析ツールとは

コードを実行せずにコードをチェックするツールです。 今回ご紹介するPHPStan以外にも様々な静的解析ツールがあります。 ツールごとにどういった観点でコードをチェックするかが異なりますので、用途に合ったものを適宜選択していく必要があります。インデントを整えるもの、コードの中のバグを見つけるもの、依存関係の確認をするものなど様々な種類があります。

PHPStanについて

PHPStanは静的解析ツールの一つです。コードを実行することなく、コード内のエラーを指摘してくれます。下記のような観点でコードをチェックします。(公式ページのWhat it currently checks for?を翻訳し、引用しています。)

  • instanceof、catch、typehints、およびその他の言語構造で使用されるクラスの存在。
  • 呼び出されたメソッドと関数の存在とアクセシビリティ。渡された引数の数。
  • メソッドが、返すように宣言したのと同じ型を返すかどうか。
  • アクセスされたプロパティの存在と可視性。また、宣言されたタイプとは異なるタイプがプロパティに割り当てられているかどうか。
  • フォーマット文字列に基づいて、sprintf / printf呼び出しに渡されるパラメーターの数。
  • ブランチとループのスコープの中に変数が存在するかどうか。
  • (string) 'foo'のような無意味なキャストと、異なる型を厳密に比較していて常にfalseになるオペランド

少しわかりにくいので実際の指摘画面をご覧いただきイメージを膨らませていただけたらと思います。 f:id:logicalarts:20201224134451p:plain

sayHello関数の中で未定義の$nameという変数を使おうとすると、上記のように未定義であることを指摘してくれます。こういったミスをコードを実行することなく修正できるようになり、コードの品質を上げることができます。

PHPStanのトップページに上記の画面のplaygroundがあるので、ブラウザ上でPHPのコードを書き解析を試すことができます。

FuelPHPにPHPStanを導入

前提条件

  • 使用したOSはAmazon Linux 、Versionは2018.03
  • FuelPHPのプロジェクトは作成済(今回はsampleとします)
  • PHPの依存性管理ツールのComposerがインストール済
  • コマンドは全てsampleディレクトリ直下で実行
  • phpstan.neonファイルはsampleディレクトリ直下に配置

インストール方法

Composerを使用します。下記のコマンドでインストール可能です。

composer require --dev phpstan/phpstan

コマンド実行後にsample/fuel/vender/binの直下にphpstanが追加されると思います。

使い方

<path_to_phpstan> analyse <target_file>コマンドを実行することで解析が可能です。

今回の私の場合は下記のコマンドです。

fuel/vendor/bin/phpstan analyse <target_file>

設定

phpstan.neonという設定ファイル、またはコマンドラインのオプションで設定を行います。

解析時の指摘ルールの厳しさを表すlevelオプションのみ設定が必須です。

その他にも様々な設定項目があり、公式のConfig Referenceに詳細な記載がありますので、こちらの中からやりたいことを見つけていくのが早いのかなと思います。

FuelPHPに導入する際の拡張元のクラスの読み込みの設定

下記のようなsample/fuel/app/classes/controller/sample.phpの解析を行う場合を考えます。

sample.php

<?php
 
class Controller_Sample extends Controller_Template
{
    protected $data = array();

    protected $header_link = true;

    public function before() {
        if (!empty($this->template) and is_string($this->template)) {
            // Load the template
            $this->template = \View::forge($this->template);
        }

        return parent::before();
    }

}

sample/fuel/app/classes/controller/sample.phpで定義されているController_Sampleクラスはsample/fuel/core/classes/controller/template.phpで定義されているController_Templatesクラスを拡張しており、さらにController_Templatesは、sample/fuel/core/classes/controller.phpで定義されているControllerクラスを拡張しているという状況です。

f:id:logicalarts:20201224132428p:plain

phpstan.neon

parameters:
    level: 7

設定必須のlevelオプションのみ上記のようにphpstan.neonに設定を行い解析を行うと、下記のエラーが出ます。

$ phpstan analyse fuel/app/classes/controller/sample.php 
Note: Using configuration file /home/sample/phpstan.neon.
 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------------------------- 
  Line   sample.php                                                            
 ------ -------------------------------------------------------------------- 
         Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  6      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  6      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  6      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  8      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  8      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
  8      Reflection error: Controller_Template not found.                    
          Learn more at https://phpstan.org/user-guide/discovering-symbols  
 ------ -------------------------------------------------------------------- 

                                                                                                                        
 [ERROR] Found 7 errors                                                                                                                                                                                                                                                                                               
                                                                                                                        

Controller_Sampleクラスの拡張元であるController_Templateクラスが読み込めないという問題が発生しました。

PHPStanにはデフォルトで読み込む部分以外を読み込む場合には、Discovering Symbolsの機能を使う必要があります。

今回の場合は、FuelPHPの読み込みのルールを、PHPStanの機能のDiscovering SymbolsのbootstrapFilesオプションで設定する必要があるのではないかと推測していました。しかし、どのファイルを指定すればよいか分からず困ってしまいました。

解決策

sampleディレクトリ直下にあるoilというファイルをbootstrap.phpに設定することで、拡張元のクラスの読み込みが可能となりました。実際には、oilをコピーしbootstrap.phpとして保存し、不要なLoad oil packageFire up the command line interfactの部分をコメントアウトし、そちらを読み込むように設定しています。現状は問題なく動いていますが、より良い方法があるかもしれません。

phpstan.neon

parameters:
    level: 7
    bootstrapFiles:
      - bootstrap.php

bootstrap.php

<?php
/**
 * Fuel is a fast, lightweight, community driven PHP 5.4+ framework.
 *
 * @package    Fuel
 * @version    1.8.1
 * @author     Fuel Development Team
 * @license    MIT License
 * @copyright  2010 - 2018 Fuel Development Team
 * @link       http://fuelphp.com
 */

/**
 * Refuse to run oil when called from php-cgi !
 */
if (substr(php_sapi_name(), 0, 3) == 'cgi')
{
    die("The use of oil is not supported when running php-cgi. Oil needs php-cli to function!\n\n");
}

/**
 * Set error reporting and display errors settings.  You will want to change these when in production.
 */
error_reporting(-1);
ini_set('display_errors', 1);

/**
 * Website document root
 */
define('DOCROOT', __DIR__.DIRECTORY_SEPARATOR);

/**
 * Path to the application directory.
 */
define('APPPATH', realpath(__DIR__.'/fuel/app/').DIRECTORY_SEPARATOR);

/**
 * Path to the default packages directory.
 */
define('PKGPATH', realpath(__DIR__.'/fuel/packages/').DIRECTORY_SEPARATOR);

/**
 * The path to the framework core.
 */
define('COREPATH', realpath(__DIR__.'/fuel/core/').DIRECTORY_SEPARATOR);

// Get the start time and memory for use later
defined('FUEL_START_TIME') or define('FUEL_START_TIME', microtime(true));
defined('FUEL_START_MEM') or define('FUEL_START_MEM', memory_get_usage());

// Load in the Fuel autoloader
if ( ! file_exists(COREPATH.'classes'.DIRECTORY_SEPARATOR.'autoloader.php'))
{
    die("No composer autoloader found. Please run composer to install the FuelPHP framework dependencies first!\n\n");
}

// Load in the Fuel autoloader
require COREPATH.'classes'.DIRECTORY_SEPARATOR.'autoloader.php';
class_alias('Fuel\\Core\\Autoloader', 'Autoloader');

// Boot the app
require APPPATH.'bootstrap.php';

// Load oil package
// \Package::load('oil');

// Fire up the command line interfact
// \Oil\Command::init($_SERVER['argv']);

上記の設定を行った結果下記のような出力が得られ、問題なく解析が行えていることが分かります。

$ phpstan analyse fuel/app/classes/controller/sample.php 
Note: Using configuration file /home/sample/phpstan.neon.
 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%


                                                                                                                        
 [OK] No errors                                                                                                         
                                                                                                                        

まとめ

FuelPHPにPHPStanを導入する際に、拡張元のクラスの読み込みに苦戦した部分について紹介しました。 FuelPHPに静的解析を導入することをお考えの方にお役に立てれば嬉しいです。

参考資料

PHPStan のドキュメント

Getting Started | PHPStan

PHPStan機能の概要

[PHP] 静的解析ツールPHPStanの機能概要 - Qiita

株式会社メルカリ様での活用方法

PHPStanで始めるPHPのための静的解析 #phperkaigi | メルカリエンジニアリング