CakePHP2ではほとんどコード補完が働きません。 今回はlaravel-ide-helperのアプローチを参考に、CakePHP2でもコード補完されるようにした話をします。
https://packagist.org/packages/nazonohito51/cakephp2-ide-helper
© - BASE, Inc.CakePHP でもPhpStormがコード補完してくれるようにした話川島 慧 / BASE Inc./ / PHPカンファレンス北海道
View Slide
© - BASE, Inc.Product Division 川島 慧@nazonohito
課題
© - BASE, Inc.CakePHP ではPhpStormはほとんどコード補完してくれない
© - BASE, Inc.Userモデルのオブジェクトが返ってくるが、そのメソッドが補完されない
© - BASE, Inc.CakePHP は動的型付けバリバリだった時代のPHPフレームワーク
© - BASE, Inc.実⾏時じゃないと⾊々確定しづらい
© - BASE, Inc.=静的解析が通⽤しづらい
© - BASE, Inc.=コード補完されないものが多い
© - BASE, Inc.つらい
© - BASE, Inc.なんとかしよ
解決のためのアイディア
photos by @@@https://hoge.dom/laravel-ide-helper
© - BASE, Inc.laravel-ide-helperとは• PhpStormが補完できないLaravelのあれやそれやを補完できるようにしてくれる君• FacadeとかModelのプロパティやら• 対象のリポジトリを解析してPhpStorm向けのメタデータを書いたファイルを作ったり、PHPDocを追記したりして実現している
© - BASE, Inc.このアプローチを流⽤すればいけるのでは?
photos by @@@https://hoge.dom/作った
ClassRegistry::init()の解決
© - BASE, Inc.ClassRegistry::init()を補完する• 引数にModelクラスの名前を⽂字列で渡すと、そのModelクラスのオブジェクトが返ってくる• プラグインのModelの場合は “{プラグイン名}.{Model名}"という⽂字列を渡すItemモデルのオブジェクトが返ってくる
© - BASE, Inc.ClassRegistry::init()の課題• ClassRegistry::init()は引数によって戻り値の型が変わる、いわゆるファクトリパターンな側⾯がある• Laravelにも同様の課題がapp()やApp::make()などにあるUserモデルのオブジェクトが返ってくるはずだが、そのメソッドが補完されない
© - BASE, Inc.laravel-ide-helperにおける解決⽅法
© - BASE, Inc.laravel-ide-helperにおける解決⽅法これなに?
© - BASE, Inc..phpstorm.meta.php
photos by @@@https://hoge.dom/https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html
© - BASE, Inc..phpstorm.meta.phpとは• PhpStormの補完機能を改善するためのメタデータを記述できるファイル• PhpStormはどのPHPプロジェクトにも共通して当てはまるような解析しかしないが、プロジェクト固有の要求に応えられるようにする機能
© - BASE, Inc..phpstorm.meta.phpで出来ること• ファクトリパターンなメソッドの引数と戻り値の型の組み合わせを定義して補完可能にする• メソッドの引数候補を定義して補完可能にする• メソッドの戻り値の値候補を定義して補完可能にする
© - BASE, Inc.これを使ってClassRegistry::init()を補完するぞ
© - BASE, Inc.CakePHP のディレクトリ構造※࣮ߦ࣌ʹطఆͷมߋ͢Δ͜ͱग़དྷ·͕͢ɺͦͷ߹ϥΠϒϥϦʹਖ਼͍͠ҾΛ͞ͳ͍ͱਖ਼ৗͳղੳͰ͖·ͤΜrepos root/app/Model/PluginSomeModel .php/SomePluginSomeModel .php/ModelSomeModel .phpSomeModel .phpここに置いてあるクラスの名前を全部glob()とかで取得するここに置いてあるクラスの名前を全部glob()とかで取得するPlugin配下のModelは、どのPluginに所属しているかも解析する
© - BASE, Inc..phpstorm.meta.phpの作り⽅• リポジトリルートに .phpstorm.meta.php という名前のファイルを作る• (実はリポジトリルート以外に置いても⼤丈夫)• (もっと⾔うと.phpstorm.meta.phpというディレクトリを作ってその下に置くでも可)
© - BASE, Inc.メタデータの書き⽅• PHPSTORM_METAという名前空間配下に⽤意されたPhpStormの関数を使ってメタデータを定義する「メタデータを記述」って聞くと、ymlやjsonみたいなstaticな構造データを書くイメージだけど、.phpstorm.meta.php上に関数を呼び出すPHPコードを書いて定義するというとても不思議な作り‧‧‧。
© - BASE, Inc.メタデータの書き⽅PHPSTORM_META配下としてコードを書く\PHPSTORM_META\override()に以下を渡す第1引数:補完したいメソッド第2引数:引数と戻り値の型のマッピング情報
© - BASE, Inc.補完されるようになった
Behaviorのメソッド補完の解決
© - BASE, Inc.課題その2• Behaviorのメソッドが補完されないContainableBehaviorのメソッドが使えるはずだが、補完されない
© - BASE, Inc.CakePHP のBehaviorについて• 複数Modelで共通した振る舞いを再利⽤可能な形で定義する機能ModelModelModelBehaviorBehaviorのpublicメソッドが使えるようになる
© - BASE, Inc.Behaviorによって追加されたメソッドは補完されない• Modelクラスの__call()によって実現している• 未定義メソッドの呼び出しはBehaviorへ委譲する形で実装されている• Laravelの場合、同様の問題がModelのクエリビルダの呼び出しに起こっている
© - BASE, Inc.laravel-ide-helperの解決⽅法ModelクラスのPHPDocがlaravel-ide-helperから書き⾜されている今回の場合は @mixin が注⽬ポイント
© - BASE, Inc.laravel-ide-helperの解決⽅法_ide_helper.phpという、名前の通りIDE向けのファイルが追加されている本来のクラスを継承した独⾃クラスを作り、補完してほしいメソッドをこちらに実装している
© - BASE, Inc.IDE向けのクラスを仲介させることで補完可能にしている
© - BASE, Inc.このアプローチでBehaviorを補完するぞ
© - BASE, Inc.やること. 全てのBehaviorクラスを捕まえる. Behaviorを模したクラスを作る. 上記のクラスをModelのPHPDocに@mixinとして追記する
© - BASE, Inc.1. 全てのBehaviorを捕まえる※࣮ߦ࣌ʹطఆͷมߋ͢Δ͜ͱग़དྷ·͕͢ɺͦͷ߹ϥΠϒϥϦʹਖ਼͍͠ҾΛ͞ͳ͍ͱਖ਼ৗͳղੳͰ͖·ͤΜrepos root/app/Behavior/PluginSomeBehavior .php/SomePluginSomeBehavior .php/BehaviorSomeBehavior .phpSomeBehavior .phpここに置いてあるクラスの名前を全部glob()とかで取得するここに置いてあるクラスの名前を全部glob()とかで取得する/Model/Model
© - BASE, Inc.. Behaviorを模したクラスを作る• 同じメソッドを持っているだけではだめ• Behaviorのメソッドの呼び出し時は第1引数を⾶ばして、第2引数以降のみを渡す• 第1引数はフレームワークが代わりに⼊⼒する=第1引数を取り除いた状態のフェイクのクラスを作る
© - BASE, Inc.第1引数を除いたメソッドを持つクラスを作る• php-parserを使うと楽にできる• PHPコードをパースするライブラリ• PHPコードをAST(Abstract Syntax Tree)に変換できる• ASTからPHPコードに戻すことも出来る
© - BASE, Inc.php-parserで第1引数が除去されたBehaviorを作るClass_ClassMethodParamParamClassMethodParamParamParamParam
© - BASE, Inc.php-parserで第1引数が除去されたBehaviorを作るClass_ClassMethodParamParamClassMethodParamParam
© - BASE, Inc.php-parserで第1引数が除去されたBehaviorを作るClass_ClassMethodParamParamClassMethodParamParam※最終的には⾊々あって少しだけ違うアプローチになりましたが、だいたいこんな感じの実装でやってます
© - BASE, Inc.php-parserによる変換前後の⽐較変換前 変換後
© - BASE, Inc.php-parserから作ったフェイクのBehaviorを_ide_helper.phpというファイルへ
© - BASE, Inc.. ModelのPHPDocに@mixinとして追記する• barryvdh/reflection-docblockを使って既存のクラスのPHPDocを編集する• https://packagist.org/packages/barryvdh/reflection-docblock
こんな感じでBASEでは、フレームワーク‧ライブラリをただのユーザとして利⽤するだけでなく、
それ以外のフレームワークの知⾒や、⼀般化された技術など活⽤しながら技術環境を改善しています
ご清聴ありがとうございました