VimでPHPのオートローダー(vendor/autoload.php)を認識し補完や定義ジャンプを賢く行う(ついでにLaravelのファサードも補完)

はじめに

個人的に最近はPythonやJavaScript(JSはみんなそうですよね)を触ってたりしますがPHPネタです。そしてVimネタです。 いや、むしろサーバーやクラウドなどのインフラをイジイジばかりか、コードのレビューをするくらい。コード書いてない。

さて、Vimにはphpcompleteというプラグインが同梱されています。

VimでPHPを書く際に補完、定義元ジャンプをするには標準ではタグファイルを作ってその情報を元に頑張るって方法がスタンダードかと思います。

タイトルにも書きましたが今回紹介するプラグインを使うとComposerの /vendor/autoload.php を認識して賢く、高速に補完、定義元ジャンプができます。

また、その仕組をフックして Vimから見えるPHPの環境 を変更することもできます。

PhpStormでいいじゃん って意見を言われてしまうので一応

はい。その通りです。PHP開発で現在最強のエディタ/IDEは PhpStorm で間違いないと思います。異論はありません。僕も持ってますよ。

また、最近は JetBrains 公式マニュアル日本語版β というものもあります。日本語マニュアル最高ですね。見やすい。

PhpStormでいいじゃん って人は PhpStorm 使いましょう。

ただ、状況によってはPhpStormがよしなにやってくれないほうが良いものもあるかもしれません。

例えばですが よしなにやってくれないから このメソッドの返り値でオブジェクトは何が返されてるのかなどPsySH なんかのREPLを使って調べたりするようなこととかやるかもしれませんしね。

まー。それでも基本はPhpStormを使いましょう。

phpcd.vim の紹介

Vimに同梱されている phpcomplete.vim をforkして作られた phpcd.vim というプラグインを紹介します。

phpcd.vim はPHPのReflection機構を使用してコンテキスト情報を取得するため、Vimに同梱されている phpcompete.vim より 賢く、高速になります。タグファイルを使わないのでタグファイルを作るなどの諸々の作業も必要ありません。

  • GitHub

  • 要件

    • PHP5.6以上
    • PCNTL拡張 が有効になっていること
    • Vimの場合は JSON拡張 が必要でNeoVimの場合は Msgpack拡張 が必要

残念ながらドキュメントはこのリポジトリの README.md しかありません。Vimのヘルプもありません。作者は中国の方です。ソース内に若干中国語のコメントがありました。

基本的にはデフォルトの状態で動きますが、まずは1つだけ phpcd.vim のオプションの設定を説明します。あとでこの値をどうのこうのしますよ。

" デフォルトの設定です。
let g:phpcd_autoload_path = 'vendor/autoload.php'

あとは Vimを起動するカレントディレクトリに .phpcd.vim というファイルがあればそれを読み込みます。 .phpcd.vim に 設定を変更(上書き)したい内容を記載するといった使い方をします。

" ...省略
function! s:init() " {{{
    let g:phpcd_root = phpcd#GetRoot()
    let phpcd_vim = g:phpcd_root.'/.phpcd.vim'
    if filereadable(phpcd_vim)
        exec 'source '.phpcd_vim
    endif
" ... 省略

感の良い人、凄腕PHPerの方々、声の大きなPHPerの方々、PHPの毛深いの方々はもうこの段階で何ができるのかなんとなく想像付くかもしれませんね。

phpcd.vimのプラグインのインストール

個人的にはvim-plugを使用していますのでその流れで記載します。
その他のプラグインマネージャーの方は お使いのプラグインマネージャーに読み替えてください。

call plug#begin('~/.vim/plugged')

" ... 色々省略

" vim-plugの説明を軽くしておきます
" 
" for はファイルタイプがphpならプラグインを読み込む 
" do はプラグインインストール、アップデート時に実行する内容 
Plug 'lvht/phpcd.vim', { 'for': 'php', 'do': 'composer install' }

" ... 色々省略

call plug#end()

というかこれはプラグインのREADME.mdに書いてますね。プラグインマネージャーを使ってない方は泣きながら手動で入れるか、詳しい先輩に聞いてください。

https://github.com/lvht/phpcd.vim#installation--usage

monologを使って実際にみてみましょう

補完、定義元ジャンプのお話をしていますので実際に手を動かしてみましょうか。

Composerで何か入れてサンプルをと思います。

Composerといえば...そうですね。 monolog ですね。作者が一緒。 PHPのスタンダードなロガーです。

ということで monolog のこちらのコードっぽくやってみましょうか

https://github.com/Seldaek/monolog#basic-usage

monologサンプルの下準備

# monologacmeってディレクトリにしますか
mkdir monologacme
cd monologacme

# npmだと-yなのでよく間違えます。-nって。-nって。。-y空いてるよ。
composer init -n

# monologのインストール
composer require monolog/monolog

# main.phpってファイルを作りますか
vim main.php

補完

はい。補完できてますね。 C-X C-O です。

1_monolog1.png

docblock

補完候補の選択時に上部のウィンドウにdocblockも表示されてますね。便利ですね。

2_monolog2.png

定義元ジャンプ

定義元ジャンプをしました。 C-] ですね。
個人的にはウィンドウをスプリットして定義元ジャンプするのが多い派なので C-W C-] です。

3_monolog3.png

プラグインの中ではこのように 定義元ジャンプ元に戻る が設定されています。

https://github.com/lvht/phpcd.vim/blob/master/ftplugin/php_phpcd.vim#L4-L11

  • 対象にジャンプ: C-]
  • 対象を横分割でジャンプして開く: C-W C-]
  • 対象を縦分割でジャンプして開く: C-W C-\
  • ジャンプ先で元の対象に戻る C-t

個人的には更に <Leader>d にも割り当てたりしますのでそれを元にカスタマイズ例とします。 .vimrc にこのように書いたりします。

autocmd FileType php nnoremap <Leader>d :call <C-U>call phpcd#JumpToDefinition('split')<CR>

ユースケースとして。他言語(GoとかPythonとかJavaScriptとか諸々)のファイルでも <Leader>d で定義元ジャンプをするって形で統一してるので、まー。そんな使い方ですね。

Slimの例で変数アノテーションでの補完や定義元ジャンプ

今度はSlimを使って手を動かしてみましょうか。

マイクロフレームワークのslimでslim-skeletonを使った例です。

https://github.com/slimphp/Slim-Skeleton

# slimacmeってディレクトリにしますか
composer create-project slim/slim-skeleton slimacme
cd slimacme

# routes.php を例にしましょう
vim src/routes.php

これは routes.php です。

4_slim1.png

この1ファイルの状況だけでは 8行目の $app って何?って状態です。

8行目の時点での $app->get メソッド を補完したり get に定義元ジャンプしたりはできません。

しかし、16行目以降に変数アノテーションの例を書いてます。

ほら?バッチリ補完できてますね。

次は定義元ジャンプもできましたね。

5_slim2.png

LaravelでFacadeを補完したい(.phpcd.vimファイルを使います)

今度はLaravelを使って手を動かしてみましょうか。

あまりFacadeのことを書くとFacade警察がきたりと怖い思いをするかもしれませんが。。とりあえず責務を〜...以下省略。

LaravelのFacadeについては #phpgenba でおなじみの @shin1x1 さんの記事を参照してください。

http://www.1x1.jp/blog/2014/03/laravel-facade-class.html

平たく説明するとLaraevlのフレームワークの中でゴニョゴニョとされないと、そんなstaticメソッド存在しないってものですね。

実行されないと存在しないので補完や定義元ジャンプをするのは不可能ですね。(通常では)

PhpStormではlaravel-ide-helperというツールを使いメタファイルを吐き出して補完や定義元ジャンプをしています。

https://github.com/barryvdh/laravel-ide-helper

同じ流れでVimからそのメタファイルを読み込んで補完や定義元ジャンプを行うといった方法をやります。

Laravelでの準備

Laravelには5.3からチュートリアルがありませんのでどういうサンプルが良いか考えるのが面倒。。

とりあえずcreate-projectで進めます。

# laravelacmeってディレクトリにしますか
composer create-project laravel/laravel laravelacme
cd laravelacme

# laravel-ide-helperのインストール
composer require --dev barryvdh/laravel-ide-helper
# laravel-ide-helperのide-helper:modelsコマンドで必要なためインストール
composer require --dev doctrine/dbal

# laravel-ide-helperのide-helper:modelsコマンドはDBが存在しないと駄目
# ゆるふわにsqliteで用意
touch database/database.sqlite
# .envファイルのDB_CONNECTIONをsqliteに変更
sed -i -e 's/^DB_CONNECTION=mysql/DB_CONNECTION=sqlite/' .env
# sqliteを使うので不要部分をコメントアウト(コメントアウトしないと怒られる)
sed -i -e 's/^DB_HOST=/#DB_HOST=/' .env
sed -i -e 's/^DB_PORT=/#DB_PORT=/' .env
sed -i -e 's/^DB_DATABASE=/#DB_DATABASE=/' .env
sed -i -e 's/^DB_USERNAME=/#DB_USERNAME=/' .env
sed -i -e 's/^DB_PASSWORD=/#DB_PASSWORD=/' .env

# コントローラーも作っておきましょうか。namespace下の説明で使うので。
php artisan make:controller TaskController

# migrateでテーブル作成
php artisan migrate

Fluent methodsのためにconfig/ide-helper.phpを作成する

あとで実行する ide-helper:generate のコマンドにて Fluent methods も含める場合はこちらの対応が必要です。

https://github.com/barryvdh/laravel-ide-helper#automatic-phpdocs-generation-for-laravel-fluent-methods

laravel-ide-helperのconfig設定を上書きするといった内容になります。

<?php

return [

    'include_fluent' => true,

];

laravel-ide-helperのconfig設定(デフォルト値)自体はこちらになりますね。

https://github.com/barryvdh/laravel-ide-helper/blob/master/config/ide-helper.php

というか、これくらい、ide-helperのコマンドでオプション用意してよって感じもありますが。

ide-helperのコマンドを使い各種メタファイルの作成

各種FacadeとHelperとFluentのが作成されます。

# _ide-helper.phpというファイルが作成されます
php artisan ide-helper:generate -H
# モデルの各種ドックコメントとか追加
php artisan ide-helper:models -W

Vim(phpcd.vim)で上記メタファイルを読み込むための準備

phpcd_autoload.php(PHPファイル)の作成

プロジェクトルートにてこのようなファイルを作成してください。
名称は今回の説明では phpcd_autoload.php とします。

<?php
// これはComposerのオートロードです
require './vendor/autoload.php';
// これはlaravel-ide-helperを使って作成されたファイルです
require './_ide_helper.php';

// php artisan ide-helper:modelsでファイル吐き出しをした場合はこちらも
//require './_ide_helper_models.php';

.phpcd.vim(Vim script)の作成

Vim(phpcd.vim)からみえるPHPのオートローダーパスを先程作成したファイルを指定して変更する

let g:phpcd_autoload_path = 'phpcd_autoload.php'

さてプロジェクトルートでVimを立ち上げてみましょう

プロジェクトルートはこの例ですと laravelacme直下 になります。

ファサード補完

Laravelのrouteファイルで試してみましょう。

:e routes/web.php をファイルを開きます。

一番複雑なDBのファサードが補完できてますね。

6_lv1.png

ファサードでのメソッドチェーンも補完できてます

メソッドチェーンもバッチリですね(ある程度)

7_lv2.png

namespace下の場合

グローバルなネームスペース環境にファサードは読み込まれてますので バックスラッシュ(\)を忘れずに。

8_lv3.png

ちなみにPhpStormではバックスラッシュなしでも補完できます。

ファサードについて補足

よく使われるRouteファサードについてはこのようにフレームワーク側でドックコメントを頑張って書いてくれてます。

useすればstaticメソッドも呼び出すことが可能です。

コードを抜粋します。

<?php

namespace Illuminate\Support\Facades;

/**
 * @method static \Illuminate\Support\Facades\Route get(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route post(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route put(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route delete(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route patch(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route options(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route any(string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route match(array|string $methods, string $uri, \Closure|array|string|null $action = null)
 * @method static \Illuminate\Support\Facades\Route prefix(string  $prefix)
 * @method static \Illuminate\Routing\PendingResourceRegistration resource(string $name, string $controller, array $options = [])
 * @method static \Illuminate\Routing\PendingResourceRegistration apiResource(string $name, string $controller, array $options = [])
 * @method static \Illuminate\Support\Facades\Route middleware(array|string|null $middleware)
 * @method static \Illuminate\Support\Facades\Route substituteBindings(\Illuminate\Support\Facades\Route $route)
 * @method static void substituteImplicitBindings(\Illuminate\Support\Facades\Route $route)
 * @method static \Illuminate\Support\Facades\Route as(string $value)
 * @method static \Illuminate\Support\Facades\Route domain(string $value)
 * @method static \Illuminate\Support\Facades\Route name(string $value)
 * @method static \Illuminate\Support\Facades\Route namespace(string $value)
 * @method static \Illuminate\Support\Facades\Route where(array|string $name, string $expression = null)
 * @method static \Illuminate\Routing\Router group(\Closure|string|array $value)
 * @method static \Illuminate\Support\Facades\Route redirect(string $uri, string $destination, int $status = 301)
 * @method static \Illuminate\Support\Facades\Route view(string $uri, string $view, array $data = [])
 *
 * @see \Illuminate\Routing\Router
 */
class Route extends Facade

今度は先程説明したDBファサードをみてみましょう。

<?php

namespace Illuminate\Support\Facades;

/**
 * @see \Illuminate\Database\DatabaseManager
 * @see \Illuminate\Database\Connection
 */
class DB extends Facade

@see って。 @see って。。 @see って。。。

Route側に書いたならせっかくなら他ファサードでも書けるものだけでも良いから書いてよっていうのが本音ですがね。

まとめ

いかがでしたでしょうか?PHPでもVimで補完や定義元ジャンプがある程度、快適になります。(ある程度)

正直、言うと諸々細かな部分はまだまだなところもあります。チェーンメソッドのチェーンメソッドのチェーンメソッドとか。それこそ小さな不具合とか。

他言語では例えば補完なら PythonならJedi, JavaScriptならtern, Goならgocode, TypeScriptならtsserverなどスタンダードな補完エンジンのツールがあったり言語自体が用意していたりします。

残念ながら今のところPHPはありません。あるなら教えてください。

現在ではLSP(language server protocol)なんてものもありますが個人的に使用した印象としてはまだまだなところも感じてます。あくまで仕様の統一をといった段階で快適か、便利かといったところのフェイズに到達するには時間がかかるでしょう。

phpcd.vim を使って気になるところはPRでもしてください。便利になったら僕がハッピーなので。

もしくは phpcd.vim は Vimとの諸々のやり取りはVim scriptですが処理などの部分はPHPで出来ています。
nikic/PHP-Parser を使って頑張ってる感じです。

この記事を読んだ凄腕PHPerの方々、声の大きなPHPerの方々、PHPの毛深い方々が よし、俺がPHPの補完エンジンを作ってやる! となるのを期待してます。

改めてですが便利になったら僕がハッピーなので。

宜しくお願いします!!

次回予告

次回はLinter(phpcs)やFormatter(phpcbfとかphp-cs-fixer)とVimで連携することやPhpStormのShift×2回相当なことをやるとか。PHPのドキュメント出したりとか。ファイル内に宣言されているクラス、変数、メソッドの一覧を出す方法とか。スニペットで今回お話した補完とは別の粒度のcompletefuncのお話(入力しながら候補をしぼっていくやつ)とか。ファイラーとか。そんな粒度で他言語もとか。

諸々をお伝えするかもしれませんが お伝えしない かもしれません(あまのじゃく)

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.