Slide 1

Slide 1 text

© - BASE, Inc. CakePHP でもPhpStormが コード補完してくれるようにした話 川島 慧 / BASE Inc. / / PHPカンファレンス北海道

Slide 2

Slide 2 text

© - BASE, Inc. Product Division 川島 慧 @nazonohito

Slide 3

Slide 3 text

課題

Slide 4

Slide 4 text

© - BASE, Inc. CakePHP では PhpStormはほとんど コード補完してくれない

Slide 5

Slide 5 text

© - BASE, Inc. Userモデルのオブジェクトが返ってくるが、 そのメソッドが補完されない

Slide 6

Slide 6 text

© - BASE, Inc. CakePHP は 動的型付けバリバリだった時代の PHPフレームワーク

Slide 7

Slide 7 text

© - BASE, Inc. 実⾏時じゃないと ⾊々確定しづらい

Slide 8

Slide 8 text

© - BASE, Inc. =静的解析が通⽤しづらい

Slide 9

Slide 9 text

© - BASE, Inc. =コード補完されないものが多い

Slide 10

Slide 10 text

© - BASE, Inc. つらい

Slide 11

Slide 11 text

© - BASE, Inc. なんとかしよ

Slide 12

Slide 12 text

解決のためのアイディア

Slide 13

Slide 13 text

photos by @@@ https://hoge.dom/ laravel-ide-helper

Slide 14

Slide 14 text

© - BASE, Inc. laravel-ide-helperとは • PhpStormが補完できないLaravelのあれやそれや を補完できるようにしてくれる君 • FacadeとかModelのプロパティやら • 対象のリポジトリを解析してPhpStorm向けのメタ データを書いたファイルを作ったり、PHPDocを追記 したりして実現している

Slide 15

Slide 15 text

© - BASE, Inc. このアプローチを 流⽤すればいけるのでは?

Slide 16

Slide 16 text

photos by @@@ https://hoge.dom/ 作った

Slide 17

Slide 17 text

ClassRegistry::init()の解決

Slide 18

Slide 18 text

© - BASE, Inc. ClassRegistry::init()を補完する • 引数にModelクラスの名前を⽂字列で渡すと、その Modelクラスのオブジェクトが返ってくる • プラグインのModelの場合は “{プラグイン 名}.{Model名}"という⽂字列を渡す Itemモデルのオブジェクトが 返ってくる

Slide 19

Slide 19 text

© - BASE, Inc. ClassRegistry::init()の課題 • ClassRegistry::init()は引数によって戻り値の型が 変わる、いわゆるファクトリパターンな側⾯がある • Laravelにも同様の課題がapp()やApp::make()など にある Userモデルのオブジェクトが返ってくるはずだが、 そのメソッドが補完されない

Slide 20

Slide 20 text

© - BASE, Inc. laravel-ide-helperにおける解決⽅法

Slide 21

Slide 21 text

© - BASE, Inc. laravel-ide-helperにおける解決⽅法 これなに?

Slide 22

Slide 22 text

© - BASE, Inc. .phpstorm.meta.php

Slide 23

Slide 23 text

photos by @@@ https://hoge.dom/ https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html

Slide 24

Slide 24 text

© - BASE, Inc. .phpstorm.meta.phpとは • PhpStormの補完機能を改善するためのメタデータ を記述できるファイル • PhpStormはどのPHPプロジェクトにも共通して当 てはまるような解析しかしないが、プロジェクト固 有の要求に応えられるようにする機能

Slide 25

Slide 25 text

© - BASE, Inc. .phpstorm.meta.phpで出来ること • ファクトリパターンなメソッドの引数と戻り値の型 の組み合わせを定義して補完可能にする • メソッドの引数候補を定義して補完可能にする • メソッドの戻り値の値候補を定義して補完可能にす る

Slide 26

Slide 26 text

© - BASE, Inc. これを使って ClassRegistry::init()を補完するぞ

Slide 27

Slide 27 text

© - BASE, Inc. CakePHP のディレクトリ構造 ※࣮ߦ࣌ʹطఆͷมߋ͢Δ͜ͱ΋ग़དྷ·͕͢ɺͦͷ৔߹ϥΠϒϥϦʹਖ਼͍͠Ҿ਺Λ౉͞ͳ͍ͱਖ਼ৗͳղੳ͸Ͱ͖·ͤΜ repos root /app /Model /Plugin SomeModel .php /SomePlugin SomeModel .php /Model SomeModel .php SomeModel .php ここに置いてあるクラスの名前を全部 glob()とかで取得する ここに置いてあるクラスの名前を全部 glob()とかで取得する Plugin配下のModelは、どのPluginに 所属しているかも解析する

Slide 28

Slide 28 text

© - BASE, Inc. .phpstorm.meta.phpの作り⽅ • リポジトリルートに .phpstorm.meta.php という 名前のファイルを作る • (実はリポジトリルート以外に置いても⼤丈夫) • (もっと⾔うと.phpstorm.meta.phpというディレクトリを作ってその下に置くでも可)

Slide 29

Slide 29 text

© - BASE, Inc. メタデータの書き⽅ • PHPSTORM_METAという名前空間配下に⽤意された PhpStormの関数を使ってメタデータを定義する 「メタデータを記述」って聞くと、 ymlやjsonみたいなstaticな構造データを書くイメージだけど、 .phpstorm.meta.php上に関数を呼び出すPHPコードを書いて定義するというとても不思議な作り‧‧‧。

Slide 30

Slide 30 text

© - BASE, Inc. メタデータの書き⽅ PHPSTORM_META配下としてコードを書く \PHPSTORM_META\override()に以下を渡す 第1引数:補完したいメソッド 第2引数:引数と戻り値の型のマッピング情報

Slide 31

Slide 31 text

© - BASE, Inc. 補完されるようになった

Slide 32

Slide 32 text

Behaviorのメソッド補完の解決

Slide 33

Slide 33 text

© - BASE, Inc. 課題その2 • Behaviorのメソッドが補完されない ContainableBehaviorのメソッドが使 えるはずだが、補完されない

Slide 34

Slide 34 text

© - BASE, Inc. CakePHP のBehaviorについて • 複数Modelで共通した振る舞いを再利⽤可能な形で 定義する機能 Model Model Model Behavior Behaviorのpublicメソッドが 使えるようになる

Slide 35

Slide 35 text

© - BASE, Inc. Behaviorによって追加されたメソッドは補完されない • Modelクラスの__call()によって実現している • 未定義メソッドの呼び出しはBehaviorへ委譲する 形で実装されている • Laravelの場合、同様の問題がModelのクエリビル ダの呼び出しに起こっている

Slide 36

Slide 36 text

© - BASE, Inc. laravel-ide-helperの解決⽅法 ModelクラスのPHPDocがlaravel-ide-helper から書き⾜されている 今回の場合は @mixin が注⽬ポイント

Slide 37

Slide 37 text

© - BASE, Inc. laravel-ide-helperの解決⽅法 _ide_helper.phpという、名前の通り IDE向けのファイルが追加されている 本来のクラスを継承した独⾃クラスを作り、 補完してほしいメソッドをこちらに実装している

Slide 38

Slide 38 text

© - BASE, Inc. IDE向けのクラスを仲介させ ることで補完可能にしている

Slide 39

Slide 39 text

© - BASE, Inc. このアプローチでBehaviorを補完するぞ

Slide 40

Slide 40 text

© - BASE, Inc. やること . 全てのBehaviorクラスを捕まえる . Behaviorを模したクラスを作る . 上記のクラスをModelのPHPDocに@mixinとして 追記する

Slide 41

Slide 41 text

© - BASE, Inc. 1. 全てのBehaviorを捕まえる ※࣮ߦ࣌ʹطఆͷมߋ͢Δ͜ͱ΋ग़དྷ·͕͢ɺͦͷ৔߹ϥΠϒϥϦʹਖ਼͍͠Ҿ਺Λ౉͞ͳ͍ͱਖ਼ৗͳղੳ͸Ͱ͖·ͤΜ repos root /app /Behavior /Plugin SomeBehavior .php /SomePlugin SomeBehavior .php /Behavior SomeBehavior .php SomeBehavior .php ここに置いてあるクラスの名前を全部 glob()とかで取得する ここに置いてあるクラスの名前を全部 glob()とかで取得する /Model /Model

Slide 42

Slide 42 text

© - BASE, Inc. . Behaviorを模したクラスを作る • 同じメソッドを持っているだけではだめ • Behaviorのメソッドの呼び出し時は第1引数を⾶ ばして、第2引数以降のみを渡す • 第1引数はフレームワークが代わりに⼊⼒する =第1引数を取り除いた状態のフェイクのクラスを作る

Slide 43

Slide 43 text

© - BASE, Inc. 第1引数を除いたメソッドを持つクラスを作る • php-parserを使うと楽にできる • PHPコードをパースするライブラリ • PHPコードをAST(Abstract Syntax Tree)に変換 できる • ASTからPHPコードに戻すことも出来る

Slide 44

Slide 44 text

© - BASE, Inc. php-parserで第1引数が除去されたBehaviorを作る Class_ ClassMethod Param Param ClassMethod Param Param Param Param

Slide 45

Slide 45 text

© - BASE, Inc. php-parserで第1引数が除去されたBehaviorを作る Class_ ClassMethod Param Param ClassMethod Param Param

Slide 46

Slide 46 text

© - BASE, Inc. php-parserで第1引数が除去されたBehaviorを作る Class_ ClassMethod Param Param ClassMethod Param Param ※最終的には⾊々あって少しだけ違うアプローチになりましたが、だいたいこんな感じの実装でやってます

Slide 47

Slide 47 text

© - BASE, Inc. php-parserによる変換前後の⽐較 変換前 変換後

Slide 48

Slide 48 text

© - BASE, Inc. php-parserから作った フェイクのBehaviorを _ide_helper.phpというファイルへ

Slide 49

Slide 49 text

© - BASE, Inc. . ModelのPHPDocに@mixinとして追記する • barryvdh/reflection-docblockを使って既存のクラ スのPHPDocを編集する • https://packagist.org/packages/barryvdh/ reflection-docblock

Slide 50

Slide 50 text

© - BASE, Inc. 補完されるようになった

Slide 51

Slide 51 text

こんな感じでBASEでは、 フレームワーク‧ライブラリを ただのユーザとして利⽤するだけでなく、

Slide 52

Slide 52 text

それ以外のフレームワークの知⾒や、 ⼀般化された技術など活⽤しながら 技術環境を改善しています

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

ご清聴 ありがとうございました