Phpstorm + LaravelのEloquent Modelで爆速補完したい

仕事柄PHPフレームワークLaravelを普段から触ってます。

取引先に卸す関係上、バージョンは常にLTS。*1

 

《想定読者》

  • Phpstorm等のPHP開発環境を利用している人

 (Phpstorm以外を無視した内容のため適時自分の環境に置換してください。)

  • 開発環境のコード補完効率を上げ、開発速度を上げたい人
  • LaravelのEloquent ORMで取得したインスタンスの補完が聞かなくてムズムズする人

 

《背景》

以下のようなコードを書いていた。

$team = \App\Team::find($team_id);

if( $team->isMember(auth()->user()) ) {

    // do something

}

 私は血液型がA型なので、"find"メソッドに警告が出ていることが気になった。

$team = \App\Team::find($team_id); // ←これ

if( $team->isMember(auth()->user()) ) {

    // do something

}

警告が出る原因は、Teamクラスにstatic宣言されたfindが存在しないことが理由だった。

LaravelのEloquent Modelを利用者であれば常識的なことだが、

static宣言されていないがこのコードは問題なく動作する。

警告の理由は、Eloquent Modelのマジックメソッド(__callStatic)が、以下のような実装になっているためだ。

public static function __callStatic($method, $parameters)

{

    return (new static)->$method(...$parameters);

 つまり、static宣言されていないメソッドが呼び出された場合、自動的にインスタンスを生成してメンバメソッドを呼び出している。(無理やりstaticメソッドとして呼び出す。)

 

このような実装の場合、Phpstormはfindメソッドを補完してくれないため、「あれ?そのstaticメソッドある?」的な注意を促すのである。

また、findメソッドを見つけることのできないPhpstormは、findメソッドの戻り値の型も当然把握していない。

そのため、以下の箇所まで入力しても\App\Teamクラスのインスタンスメソッドが補完されない。

$team = \App\Team::find($team_id);

if( $team->

 爆速でプログラミングを行う上で、これは由々しき事態である。(しかもPhpstormをつかっているのに…。)

 

どうにかして、Phpstorm側にfindメソッドをstaticに呼び出せると錯覚させることはできないか試行錯誤を行った。

 

《解決方法》

Eloquent Model の継承クラス(モデル)に、補完用のTraitを付与する方法で解決した。

実際のTraitは以下の通りである。

<?php

namespace App;

use Illuminate\Database\Eloquent\Collection;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

/**
* Trait ModelSupportHelper
*
* @package App
*
* @method static $this find($id, $columns = ['*'])
* @method static $this where($column, $operator = null, $value = null, $boolean = 'and')
* @method static $this whereColumn($first, $operator = null, $second = null, $boolean = 'and')
* @method static $this whereIn($column, $values, $boolean = 'and', $not = false)
* @method static $this whereNotIn($column, $values, $boolean = 'and')
* @method static $this orderBy($column, $direction = 'asc')
* @method static $this orderByDesc($column)
* @method static Collection get($columns = ['*'])
* @method static $this first($columns = ['*'])
* @method static LengthAwarePaginator paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null)
*/
trait ModelSupportHelper
{
}

上記のTraitをモデルでuseする。

Traitのphpdocとして、保持しているメソッドを記述しています。

当然ですが、phpdocはコメントのためこのTraitをuseするクラスの挙動には影響はありません。

phpdocのコツとしては、以下のように戻り値をstaticメソッドで、戻り値を$thisにすること。

* @method static $this find($id, $columns = ['*'])

すると、冒頭のコードが以下のようにfindメソッドの警告がなくなる。

$team = \App\Team::find($team_id);

if( $team->isMember(auth()->user()) ) {

    // do something

}

 さらに、以下まで入力した段階で補完候補にisMemberメソッド(インスタンスメソッド)が出てくるようになる。

$team = \App\Team::find($team_id);

if( $team->

これでモデルクラスを利用したコードの補完が効くようになり、快適なコーディングが可能になる。

 

《最後に》

中身が空っぽのTraitをuseする副作用について、知見をお持ちの方がいらっしゃればご教授ください。

*1:執筆時点(2018年3月30日)のLTSバージョン5.5を元にしてます