1. はじめに
私は職場でVimを使ってPHP(Laravel)を書いています。今回、VimをPHP開発環境として整備したいと思いこちらの記事を読んでいましたら、Phpactorというプラグインが推しで紹介されていました。
Phpactorは入力補完やリファクタリング、定義ジャンプなどが可能なとても高機能なプラグインなんですが、日本語の記事が非常に少なかったので簡単にご紹介します。
Phpactorの動作の紹介で使用しているPHPのコードはlaravel5-5-exampleを使っています。
2. Phpactorとは
↓はPhpactorを軽く動かしてみたところです。Phpactorの機能を利用している箇所は、黄色い字でその機能名を記載しています。
ここでおこなっている操作は、
- 新しいクラス
App\Models\Review
を作成するため、ファイルapp/Models/Review.php
を開きます。 - Phpactorで空のクラス定義を生成(
Class New
)。 -
Jsonable
インタフェースを実装するimplements
を追記(Omni Completion
)。
Phpactorの入力補完を使うことで、use
演算子の追記(Use Add
)も自動でやってくれています。 - コンストラクタを入力。Neosnippet.vimの
construct
スニペットを展開し、続けてコンストラクタの引数を入力しています。
引数のタイプヒントでも、Phpactorのオムニ補完とuse
の追記を利用しています。 - コンストラクタの引数を自身の同名のプロパティに代入する記述を追加し(
Complete Constructor
)、プロパティの宣言を生成(Add Missing Properties
)。 -
request
、content
プロパティへのゲッタを同名のメソッドとして作成(Generate Accessor
)。 - コードが画面からはみでたので、画面分割をおこなっています。
-
Jsonable
インタフェースで定義されているメソッドtoJson()
を作業中のファイルに取込んでいます(Implement Contracts
)。 -
toJson()
の処理内容を、Phpactorの補完を使って記述しています。 -
toJson()
メソッド内で呼出しているjson_encode()
関数の引数として記述した配列を、別メソッドtoArray()
に切出しています(Extract Method
)。
多機能さをご理解いただけるかなと思います。スマートな入力補完やリファクタリング、コードの生成代行など、IDE的な機能が満載です。公式のスクリーンショットはこちら。
公式ドキュメントによると、Phpactorは高機能な入力補完とリファクタリングツールであり、次のような特長を備えています。
- 発音は「Phpactor」
- 文脈を考慮したスマートな入力補完
- Composerに最適化されており、リアルタイムで高速に動作する
- クラスやメソッド定義元へのジャンプ機能
- クラスの移動、コンストラクタ補完、メソッド生成などのリファクタリング機能
- 公式でVimをサポート(Emacsサポートは目下製作中とのことだが、補完とか普通に動く)
- コマンドライン上で実行するツールとしても使用可能
- 自身と似たプロジェクトとして、PHP Language Serverを挙げている。Phpactor自身もLanguage Serverを制作中(ALEとの連携あたりが想定されているようです)
GitHubを見ると、現在は開発段階で最新リリースは~~0.9.0
(2018-09-13)~~ → 0.11.0
(2018-12-21)です。
3. Phpactorの導入
動作には
- PHP7.0以上
- Composer
- Vim8 / Neovim
が必要になります。参考までに、私の環境は、
- Ubuntu 18.04 / CentOS7
- Vim 8.1
- PHP 7.2
- Composer 1.6.3
- Python3.6 / Python3.4(自動補完プラグインの利用に必要)
です。
3.1. インストール
私はプラグイン管理にdein.vimを利用しています。tomlにこんな具合に書いて保存してVimを再起動し、:call dein#install()
でインストールできました。
[[plugins]]
repo = 'phpactor/phpactor'
on_ft = ['php']
build = 'composer install'
:call phpactor#Status()
で、きちんとPhpactorが導入できたか確認できます。
Info
----
Version: a516257 (7 days ago) Change-visiblity-617
Work dir: /home/cyrt/wk/repo/laravel5-5-example
Diagnostics
-----------
[✔] Composer detected - faster class location and more features!
[✔] Git detected - enables faster refactorings in your repository scope!
[✔] XDebug is disabled. XDebug has a negative effect on performance.
Config files
------------
[✘] /etc/xdg/phpactor/phpactor.yml
[✘] /home/cyrt/.config/phpactor/phpactor.yml
[✘] /home/cyrt/wk/repo/laravel5-5-example/.phpactor.yml
作業中のプロジェクトがComposerを使用していなくても、入力補完を含むPhactorの大部分の機能は使えるようです(未検証)が、プロジェクトの規模によっては速度に影響がでるかもしれない、とあります。↑のDiagnosticsにもありますが、Git・Composerとともに使うことで最大限に活用できるようです。
3.2. キーマップ
Phpactorは導入時に独自のキーマップを定義しませんので、自分で設定してやる必要があります。
公式には、設定例として<Leader>をプリフィクスにしたキーバインド例がのっています。以下はそれを少しだけ書換えたものです。書換えた箇所は、
- 定義元へのジャンプの
phpactor#GotoDefinition()
を別窓でおこなう<C-w><Leader>oを追記しています。 - カーソル下の要素の情報(変数であれば型情報、クラスであれば名前空間も含めた完全修飾名、など)を表示する
phpactor#Hover()
のキーマップを追記しています。
です。
" 画面を分割して定義元へのジャンプ
function! DefinitionJumpWithPhpactor()
split
call phpactor#GotoDefinition()
endfunction
" useの補完
nmap <silent><Leader>u :<C-u>call phpactor#UseAdd()<CR>
" コンテキストメニューの起動(カーソル下のクラスやメンバに対して実行可能な選択肢を表示してくれます)
nmap <silent><Leader>mm :<C-u>call phpactor#ContextMenu()<CR>
" ナビゲーションメニューの起動(クラスの参照元を列挙したり、他ファイルへのジャンプなど)
nmap <silent><Leader>nn :<C-u>call phpactor#Navigate()<CR>
" カーソル下のクラスやメンバの定義元にジャンプ
nmap <silent><Leader>o :<C-u>call phpactor#GotoDefinition()<CR>
" 編集中のクラスに対し各種の変更を加える(コンストラクタ補完、インタフェース実装など)
nmap <silent><Leader>tt :<C-u>call phpactor#Transform()<CR>
" 新しいクラスを生成する(編集中のファイルに)
nmap <silent><Leader>cc :<C-u>call phpactor#ClassNew()<CR>
" 選択した範囲を変数に抽出する
nmap <silent><Leader>ee :<C-u>call phpactor#ExtractExpression(v:false)<CR>
" 選択した範囲を変数に抽出する
vmap <silent><Leader>ee :<C-u>call phpactor#ExtractExpression(v:true)<CR>
" 選択した範囲を新たなメソッドとして抽出する
vmap <silent><Leader>em :<C-u>call phpactor#ExtractMethod()<CR>
" split → jump
nmap <silent><C-w><Leader>o :<C-u>call DefinitionJumpWithPhpactor()<CR>
" カーソル下のクラスや変数の情報を表示する
" 他のエディタで、マウスカーソルをおいたときに表示されるポップアップなどに相当
vmap <silent><Leader>hh :<C-u>call phpactor#Hover()<CR>
3.3. 設定
次のようなパスに設定ファイルを記述することで、Phactorの細かい挙動の制御や各種の設定ができます。
/etc/xdg/phpactor/phpactor.yml
~/.config/phpactor/phpactor.yml
{プロジェクトのルートディレクトリ}/.phpactor.yml
ざっとリファレンスを見た限りでは特に設定を書く必要はなさそうだったので、私自身はなにも設定は記述していません。
ただ、Phpactorはパフォーマンスの関係でXDebugを無効にするので、XDebugを有効にしておきたい場合は次のようにコンフィグファイルに記載します。
# 既定はtrue
xdebug_disable: false
一度試しにXDebugを有効にしたままにしてみましたが、Vimがかなり重くなる感があります。差支えなければ、デバッグ作業時以外は基本的にOFFににしておいてもよさそうです。
このほか、現在開いているバッファから対応する単体テストファイルにジャンプするための設定(ナビゲーション機能)や、新たにクラスを作成する際に基礎となるテンプレートファイルを指定したりと、様々にカスタマイズができるようです。ただ、ドキュメントを読んでもよくわからない…。
4. 機能紹介
Phpactorの機能は、入力補完、ジャンプ、リファクタ機能に大別できると思います。
このうち、ジャンプとリファクタ機能は↓でご紹介のコンテキストメニューから選択肢を辿って実行することができます。そのため、慣れないうちは「コンテキストメニューを各所で立上げて、おこないたい処理を選んで実行」という使い方をおすすめします。
4.1. 入力補完
オムニ補完
入力補完については、例えば.vimrc
に次のように記述することで、Vimの組込みオムニ補完でPhpactorを利用できます。
autocmd FileType php setlocal omnifunc=phpactor#Complete
挿入モード中に<C-x><C-o>を入力することで、オムニ補完を呼出すことができます。
Phpactorは、タイプヒントやDocコメントを見たメソッド・プロパティの補完だけでなく、メソッド内で参照可能なローカル変数なんかも(コンテキストを考慮して)補完してくれます。うえの動作例では$request
$notification
$oldValues
がオムニ補完の候補として表示されています。具体的にはこちらに載っています。
プラグインによる自動補完
オムニ補完も十分便利ですが、さらに便利な自動補完を有効にするために、非同期入力補完プラグインを使うことができます。公式に対応している入力補完プラグインとして、
が記載されています。以下でそれぞれを使ってみます。いずれの補完プラグインともに、Vim8 / Neovimがhas('python3')
やhas('timers')
である必要があるなど、動作に要件があります。
deoplete.nvim
deoplete.nvimは国産の補完フレーワークです。
Deoplete is the abbreviation of "dark powered neo-completion". It provides an extensible and asynchronous completion framework for neovim/Vim8.
deoplete.nvimでPhpactorの補完を利用する場合は、deoplete-phpactorをあわせてインストールします。
# Vim8の場合、nvim-yarpの動作に必要
[[plugins]]
repo = 'roxma/vim-hug-neovim-rpc'
if = '!has("nvim") && has("python3")'
depends = 'nvim-yarp'
[[plugins]]
repo = 'roxma/nvim-yarp'
if = '!has("nvim") && has("python3")'
[[plugins]]
repo = 'Shougo/deoplete.nvim'
if = 'has("python3")'
hook_add = '''
let g:deoplete#enable_at_startup = 1
'''
[[plugins]]
repo = 'kristijanhusak/deoplete-phpactor'
on_ft = ['php']
正しくインストールできていれば、次のようにグレートな補完ライフを享受できるようになります。
軽く使ってみた感想としては、私の作業環境では常時自動の補完候補表示を有効にしておくと、若干のカクつきは否めないかな~というところですね。このあたり、設定や環境で変わってくると思います。
ncm2
ncm2は海外製の補完フレームワークです。
NCM2, formerly known as nvim-completion-manager, is a slim, fast and hackable completion framework for neovim.
ncm2では、Vim8への対応はおまけとされています("Note that vim8 support is simply a bonus."とある)が、私が試したかぎりでは動作しました。
ncm2でPhpactorの補完を利用する場合は、ncm2-phpactorをあわせてインストールします。
# Vim8の場合、nvim-yarpの動作に必要
[[plugins]]
repo = 'roxma/vim-hug-neovim-rpc'
if = '!has("nvim") && has("python3")'
depends = 'nvim-yarp'
[[plugins]]
repo = 'roxma/nvim-yarp'
if = '!has("nvim") && has("python3")'
[[plugins]]
repo = 'ncm2/ncm2'
if = 'has("python3")'
hook_add = '''
autocmd BufEnter * call ncm2#enable_for_buffer()
set completeopt=noinsert,menuone,noselect
'''
[[plugins]]
repo = 'phpactor/ncm2-phpactor'
on_ft = ['php']
正しくインストールできていれば、次のように動いてくれます。
ncm2は、初期状態ではdeoplete.nvimよりも気持ちはやく動作する印象です。
使っていて少し痛いと感じた点として、補完候補が表示された以降もなお候補のしぼりこみのために入力を続けても、補完候補が更新されないということがときどきあります。そういうときは一文字削除してから入力を再開すると、うまく補完候補を更新してくれることがあります。ncm2が原因なのかncm2-phpactorが原因なのかわかりませんが…。
このほか、スニペット機能としてneosnippet.vimの機能を使うには、ncm2-neosnippetもあわせてインストールします。公式の設定によれば、次のような記述になります。
[[plugins]]
repo = 'ncm2/ncm2-neosnippet'
depends = ['neosnippet.vim']
hook_add = '''
" Enterキーでスニペットを展開、展開できない場合はEnterをそのまま入力する
inoremap <silent><expr><CR> ncm2_neosnippet#expand_or("\<CR>", 'n')
'''
[[plugins]]
repo = 'Shougo/neosnippet.vim'
ただ、この設定だと、スニペットの展開と次のプレースホルダへのジャンプをそれぞれ異なるキーに割当てないといけません。それぞれを意識して使い分けるのは少し不便なので、私は次のように設定しています。
imap <expr><C-l> neosnippet#expandable() ? ncm2_neosnippet#expand_or("\<Tab>", 'n') : neosnippet#jumpable() ? "\<Plug>(neosnippet_jump)" : "\<Tab>"
smap <expr><C-l> neosnippet#expandable() ? ncm2_neosnippet#expand_or("\<Tab>", 'n') : neosnippet#jumpable() ? "\<Plug>(neosnippet_jump)" : "\<Tab>"
↑は<C-l>に展開とジャンプの両方をあてています。スニペットのなかで別のスニペットを呼出す場合もありうるので、ジャンプよりも展開を優先して実行します。
↑の例では、ローカル変数$request
とそのメソッド$request->macro()
の候補表示でphpactorとncm2-phpactorが動き、$request->macro()
のスニペット展開をncm2-neosnippetとneosnippetがやってくれています。さらに展開されたスニペットの第二引数macro
プレースホルダで、function_literal
スニペットを展開しています。
メソッドの補完だけでなく、スニペット展開までセットでやってくれるのはかなり便利ですね。
その他
deoplete.nvimはたくさん日本語の紹介記事が見つかるんですが、ncm2はほぼ皆無の状態ですね。Phpactorもそうなんですけど、みんな使っていないのかな…。
4.2. 定義元ジャンプ
定義元ジャンプを使ってソースコードの移動ができます。プロパティの定義や引数・戻り値のタイプヒント、型のDocコメントなどを見てジャンプしてくれます。
宣言記述のないプロパティや動的プロパティなんかは、補完やジャンプに失敗します。型の記述があれば、↑のようにプロパティのメソッドの定義元($this->repository->update()
)にもジャンプできます。
Phpactorのジャンプは↓のようにふたつのジャンプ位置を残すので、ジャンプ前の元の場所に戻る際は、<C-o>を二度押します。
4.3. カーソルホバー
なにかしらの記述(変数や関数、メソッドなど)のうえでPhpactor:Hover()
を呼出すと、その対象についての上昇をステータスバーに表示してくれます。
While not a navigation function as such, this RPC command will show brief information about the symbol underneath the cursor.
関数やメソッドであれば、そのプロトタイプを表示してくれます。
プロパティであれば、可視性と型です。↓はuse
されているIndexable
で定義されているプロパティですが、きちんと情報を取得できています。
変数やメソッドの引数も型を表示してくれます。
情報の判別ができなかった場合は、その旨が表示されます。
手軽に型やシグネチャを知りたいときに活躍しますね!
4.4. リファクタリング
4.4.1. コンテキストメニュー
操作をおこないたいクラスやメンバにカーソルを置いた状態で<Leader>mmを入力するとコンテキストメニューが立上がり、状況に応じて実行可能な操作の一覧を表示してくれる、操作窓口的な機能です。うえで紹介しているジャンプやホバーも、コンテキストメニューからでも呼出すことができます。
立上げたメニューのキャンセルは<C-c>や<C-[>の2回打ちでできるようです。
変数にカーソルを置いた状態でメニューを立上げると、次のようなメニューが表示されます。ここでは、実行可能な操作として表示された([r]ename_variable)を呼出し、メソッドローカル変数req
→new_req
へのリネームをしています。
↓は変数にカーソルを置いた状態でメニューを立上げ、操作([f]ind reference)を実行したところです。カーソル下のCategory
クラスを参照している箇所を、ソースコードから検索してQuickFixで表示してくれます。
gtagsにも同様の機能があるので比較がてら直後に実行していますが、候補の表示量にだいぶ差がありますね…。PhpactorはタイプヒントやDocコメントを頼りに検索しているため、すべてを網羅するにはかなり行儀のいい書き方をしていなければ難しいでしょう。
4.4.2. クラス整形
メニューが開き、編集中のファイルに対する各種の整形がおこなえます(Transform
を「整形」としました)。
コンテキストメニューからも辿れますが、キーマップ<Leader>ttで直接整形に関する操作を一覧することができます。
ここでは、
- ([f]ix_namespace_class_name) .. Composerのautoload設定やファイル名に基づいて名前空間・クラス名を修正
- ([c]mplete_constructor) .. コンストラクタの補完
- ([a]dd_missing_properties) .. 宣言なしに使われているプロパティ宣言の生成
- ([i]mplement_contracts) .. インタフェースで宣言されているメソッドを編集中のクラスへ取込み
名前空間の修正 - fix_namespace_class_name
次のGIFでは、app/Models/NewItem.php
の誤った名前空間App\Models\IncorrectNamespace
を、ファイルパスを見てApp\Models
に修正しています。
ファイルの移動やクラス名・名前空間の変更などをおこなった際に役立つかもしれません。
コンストラクタの記述補完 / プロパティの宣言の生成 - complete_constructor / add_missing_properties
冒頭のGIFでも実演していますが、コンストラクタのプロトタイプを入力した状態でこの機能を呼出すと、引数と同名のプロパティの宣言と初期化の処理を記述してくれます。
↑でおこなわれた処理のうちプロパティの宣言の補完が add_missing_properties によるものですが、 complete_constructor の実行時にも呼出されるようです。
4.5. 他の機能
4.5.1. use
の追加
冒頭のGIFにもありますように、use
の追加はメソッドの引数のタイプヒントで、Phpactorの入力補完を呼出した際にやってくれますが、use
の追加のみを単独の操作として呼出すこともできます。うえのキーマップでは<Leader>uにあてられています。
↓のGIFはRequest
をインポートする様子です。複数候補がある場合、いずれかを選択するよう促されます。
4.5.2. 名前空間・クラス名の変更 / ファイルの移動
プログラムを書いていて、既存のクラスの名前空間やクラス名を変更したくなることは時折ありますが、Phpactorにはこの機能も備わっています。
リポジトリの中身を複数ファイルに渡り変更→保存するため、操作の実行には注意が必要ですが、当該クラスを参照しているファイルのタイプヒントやuse
を修正してくれます。
次はapp/Models/User.php
をapp/User.php
に変更するという例です。変更の前後でそれぞれリポジトリのステータスを確認すると、複数のファイルに対し処理をおこなっているのがわかると思います。
ただし、Docコメントの型情報までは修正されませんので手動で直す必要がありす。
5. おわりに
Phpactorを使うようになってから作業効率があがったと感じています。以前はctagsやgtagsを使って補完やタグジャンプをしていましたが、BladeやSmartyのようなテンプレートを編集する場面以外ではあまり使わなくなりました。
うえで紹介したほかにも、Phpactorには各種のIDE的機能が備わっています。開発段階であるとはいえ様々な便利機能が用意されていますので、興味をもたれた方はぜひ試してみてください。
6. 参考
-
公式です。
-
phpactor/phpactor: PHP completion, refactoring and introspection tool.
GitHubのリポジトリです。
-
Vim for PHP: The Complete Guide for a Powerful PHP IDE ・ Web technologies
この記事でPhpactorを知りました。Phpactorを"phpactor is simply a must have."と紹介していたり、他の箇所では「怪物」と評しているので、気になってPhpactorを使うきっかけになりました。
vim-bbyeなど、他にも日本では知名度が低いが便利そうなプラグインが紹介されています。 -
Phpactorの紹介スライド。
-
FANBOX開設、そしてなにとぞEmacs+PHP – USAMI Kenta @tadsan – Medium
日本語記事そのいち。
-
WIP: EmacsでインテリジェントなPHPコーディング2018 #phpconfuk_rej / 黒點 さん - ニコナレ
日本語記事そのに。Phpactor以外にも、PHP開発で役立つツールを紹介されています。
-
動作例のGIFの録画の際に使用したコードの拝借元です。