nanisore oishisou

Webエンジニアあるまさんのゆるふわ奮闘記。

ViewComposerで渡すデータをControllerで渡したデータとマージする

DOMO、最近、go言語に興味があるベル子です。 速いは正義。 話は変わって、 皆さんは、グローバルなメニューなどで使わなきゃならない値がある場合、 どうしてますか。 私がそれと初めて出くわしたのは、今の会社に入って間もなくのことでした。 グローバルメニューに設置するお知らせ機能で使うデータです。 まだLaravelのこともよく分かっておらず、 毎日Google先生とドキュメントを並べて睨めっこしながら書いていた頃です。 いや、今でもしょっちゅう睨めっこしてますねw それでどうしよーかなーと思ってStack Overflow先生に聞いてみたら ViewComposerを使ったらええよって教えてくれました。偉大。 ただ、もうその頃は5がリリースされてたんで5系の記事ばかりで、 4.2と5で何がどう変わったのかもよく知らなかった私は、4.2でどうしたらいいのか困ってしまって、先輩PGに聞いてみることにしました。 そしたら、「そんな難しいことしないでViewで直接取ってきたらいいんじゃないの」と言われて「ああ、確かに」となったわけです。 それで調子に乗って、面倒くさい時はViewから直接、値を取りまくっていたら「こういうのはControllerに書いて」と言われたんですね。 「でもパイセンもユーザーデータはViewで、毎回、取ってるじゃないですか!これと何が違うんですか?!」と言ったら、「これくらいは仕方ないんだよね」との回答で、当時はその理由がよく分かってませんでした。 「仕方ない」を要約するとこうです。 私たちの作ってるようなアプリケーションて、ログインユーザーにいろいろ紐づけられていることが多くて、ログインユーザーデータはしょっちゅう使うことになって、それを毎度毎度Controllerで設定してやるのが面倒だし、そのページのユニークの値でもないからワザワザ毎回書いて渡すのは気持ち悪い。 あとLaravelのFacade使って書けるので、Auth::user()みたいな感じで、めっちゃ短いし、Facadeだから毎回取りにいってるのとは違う。 ということなんですね。 ただ、MultiAuthになるとユーザーデータ取るときにAuth::guard('user')->user()みたいに取らないといけなくて、長くて見た目が気持ち悪いので、今、自分がやってる案件では毎度Viewにwithで渡していたんです。 そしたら一緒に書いているエンジニアさんから「これ毎回渡すの面倒くさいから、ViewComposerで弾込めしてもいいですか?」と言われたんです。 やっぱね、そうだよね。 私も何となくそれがいいんじゃないかと思ってた。 それで、ここからが本題なんですけど、ViewComposer てめっちゃ便利なんですよ。 そのViewで使う値を弾込めしておけるから、Controllerから渡してやる必要がないんで、 「このページでもこの値(view)使ってたんだー。わーマジ面倒いわー。」がなくなります。 しかもViewの指定に以下のようにワイルドカードが使えるので、このディレクトリ以下のView全部に弾込めねーも出来ちゃうという。
View::composer(
'profile/*',  'App\Http\ViewComposers\ProfileComposer'
);
すごいよ、すごいよ。 具体的にはComposerServiceProviderを追加して、そこで弾込めするviewと、ロジックを書くComposerクラスを指定するという感じです。 詳しくはドキュメントを参照。 ServiceProviderを新しく作成したときは、config/app.phpのprovidersに追加するのを忘れずに。 それで、先日、大きなUI改修があって、いろいろ弾込めしないといけない事案が発生。 ViewComposer 使ってなかったら、もうげんなりしちゃうとこですが、そう今の私にはViewComposer があるんだぜっ! だから全部使いそうな値はこっちに移してやるんだぜっ! いでよ、超絶コピペ技巧!! いやーいい汗かいたぜ。 と思ったのもつかの間、他の実装を進めていると、何やらデータがいろいろおかしい。 そしていろいろエラーが出ている。 ちゃんとControllerで設定してあるし、dumpしても値取れてるやん。 Viewでdump。 ファッ!?Controller の値とView の値がちがう。もしや呪いか!? どうしよう、近くに教会がない。。 と思ったけど、私には思い当たることがあった。 そう、昨日の超絶技巧。 原因はきっとあれや。 あれー?蘭ねーちゃん、でもComposerの値ってController ので上書きされるんじゃなかったっけ? そう書いてあった気がするんだけどなぁ(メガネキラーン)。 で、ドキュメントを確認したところ、
Just before the view is rendered, the composer's compose method is called with the Illuminate\View\View instance. 

つまり、viewがレンダリングされる直前にcomposeメソッドは呼ばれる。

なんだ書いてあるやん。しかもめっちゃ逆やん。 100万年前からそうだと思ってたけどね! というわけで、 Controllerでその変数設定してたら、そっちで上書きねーを実現するには、 以下3つの方法のどれかにする必要がある。 ・viewのデータをgetDataしてmergeする ・ComposerやめてCreatorにする(Creatorはレンダー直前じゃなくてViewがインスタンス化された直後に実行される) ・offsetExistsで、すでにkeyがあるか判定する

viewのデータをgetDataしてmerge

view()->composer(‘partials.header’, function ($view) {
$with = array_merge([
  'message' => 'This is a message on some page'
], $view->getData());
$view->with($with);
});
参考サイト

ComposerやめてCreatorにする

view()->creator('profile', 'App\Http\ViewCreators\ProfileCreator');
参考サイト

offsetExistsで、すでにkeyがあるか判定する

if($view->offsetExists('language_id')) {
$language_id = $view->offsetGet('language_id');
}
参考サイト 今回は設定するデータが多くて、すでにkeyがあるのか判定するのが面倒なので一番上のマージする方法を選択することにした。 ってここまで書いていて思ったんだけど、 ワイルドカード使ってるから同じディレクトリ配下のpartialなviewをいっぱい読み込んでいたら 全View分でデータ取ってきてマージしてってしてそう。。。 やっぱり弾込めするViewは細かく指定したほうが良さそうですね。 週明けにもう一度dumpしながら修正することにしよ。 ということで、皆様も便利なものを使うときは 使用上の注意をよく読み、用法・用量を守って正しくお使い下さい じゃないと期限日直前に呪われます。 God bless you.