【laravel】共通変数をViewComposerで渡すとよくない話

製作 プログラム

最終更新日:2023/11/01

ラムネグから一言:寝る前に読むとくだらなすぎて逆に寝れると好評なすごい適当なブログをこっちではじめてます.

laravelで共通変数を全部のページ(laravelではblade)に渡したい時って、ググるとViewComposerを使うといい!って出てきます。

ということで私も特に疑うことなくこのViewComposerを使って、例えばサイドバーに表示したい内容などを書いていました。よく使う方法としては「人気の記事」とかの記事データをこのViewComposerで共通変数化して渡してたりすると思います。

ただ今日知ったんですが、laravelのViewComposerはbladeを呼び出すたびに実行されているんです。

誤解を恐れずに書くなら共通変数ではないです。これ。結局bladeファイルをheaderとかfooterとかに分けてる場合、hedaer.blade.phpを呼び出す際に一回、footer.blade.phpを呼び出す際に1回、というふうにパーツ化してるbladeファイルごとにこのViewComposerの「__construct()」が実行されて、その結果が返されてます。

  1. つまりSQLめっちゃ発生する
  2. 解決法
  3. んじゃshare()でよくない?
  4. まとめ
  5. 追記:share()もデメリットがあった
  6. 更に追記:ミグレーションでエラーを防ぐ方法

つまりSQLめっちゃ発生する

なので「これで共通変数化して可読性上がる!」とか思ってViewComposerを使うと、そのページを構成するbladeファイルの数だけ「__construct()」が実行され、その中ではたぶんSQLを叩いてるだろうから、その数だけSQLが叩かれ…、と全然よくない。むしろダメすぎる事態に。

ViewComposerの宣言時、というか「boot()」内でどういうときにこの共通変数を読み込むかの設定をすると思います。ググって出てくる多くの記事では「ViewServiceProvider.php」を作成してその中の「boot()」をこんな感じで書いてると思います。

ViewServiceProvider.php内の「boot()」


    public function boot()
    {
        View::composer('*', 'App\Http\Composers\Common');
    }

みたいな感じ。

ここで「*」を指定してるのが非常によくない。

イメージしづらいんですが、このViewComposerてのは一つのまとまったViewが作られるときに実行されるんじゃなくて、パーツごとのbladeファイルが呼ばれるたびに実行されるんです。

なのでワイルドカードである「*」を選ぶと、上で書いた通り、一つのview内で読んでる各bladeファイルごとにこの「__construct()」が実行され、結果ものすごい意味のないSQLが発行されまくります。

解決法

SQL文の発行が増えれば増えるほどサイトの表示速度が遅くなるのでよくない。ならどうすればいいのかというと、こんな感じで解決ができます。

ViewServiceProvider.php内の「boot()」


    public function boot()
    {
        View::composer('sidebar', 'App\Http\Composers\Common');
    }

上の例だと「sidebar.blade.php」が呼ばれた場合のみViewComposerが実行されるようになります。仮にディレクトリ構成が「resources/views/common/sidebar.blade.php」と一つ自分で作った「common」というフォルダを挟んでいるとするなら上記の記述は「common/sidebar」となります。

これで1回しか呼ばれないのでパフォーマンス的にはOK!です。

んじゃshare()でよくない?

という事でViewComposerが実行されるbladeファイルを絞ることでSQLの乱発を防げ、パフォーマンス的にはまともになった、というのが前回までのお話。

ただ、それならshare()でいいんじゃない?とすんごく思います。

share()も同じく共通変数をbladeに届けるための仕組み。こっちはViewComposerとは違い、完全にグローバル変数的な扱いとなっています。

どこでもいい(ViewServiceProvider.phpでもいい)んですが、例えばよくググって出てくる例としては「AppServiceProvider.php」の「boot()」に、

ViewServiceProvider.php内の「boot()」


$posts = Post::all();
        View::share('posts', $posts);

でOK。あ、ただ冒頭できちんと「use Illuminate\Support\Facades\View;」と「use App\Models\Post;」は行ってくださいね。

これで全baldeファイルで$postsの値が使えるようになります。

ViewComposerだとその変数が使えるのはそのbladeファイル内のみだったのに対し、完全にこっちの方が使う側のイメージに合ってる動きをしてると思います。

ViewComposerを使うのってどういうタイミングなんだろう。共通変数(グローバル変数)が欲しいならshare()の方が適してるし、全部じゃないけど50%くらいの割合で表示することになるパーツに対して行うのがViewComposerなのかな、と思います。

ただそんなケースある?と思いますが。

まとめ

laravelで共通変数を宣言する方法としてよく出てくるViewComposerは個人的にあんまりよくないと感じた、という内容でした。

とにかくbladeファイル文のSQLが呼ばれるのがよくなさ過ぎる。ただグローバル変数が宣言したいだけならshare()を使う方が適してるし、ならこのViewComposerはいつ使うんだろう、という感じです。

記述としてもいちいちサービスプロバイダを設定したりと手順が多いですし、個人的に作ってるアプリに関してはshare()の方に全面的に書き換え中です。

追記:share()もデメリットがあった

そんなこんなでshare()を使うように変えてたんですが、今作ってるアプリでこんなエラーが。出たタイミングは「php artisan migrate」したタイミングです。

SQLSTATE[42S02]: Base table or view not found: 1146 Table '○○.△△' doesn't exist (Connection: mysql, SQL: select * from `△△`)

内容としては「いやー、お客さん、そんなテーブルないですよ」っていうエラーなんですが、そりゃそうで、まだmigrateしてないからテーブルないのが普通、なんですよね。

どうやらshare()のところ、というか「□□ServiceProvider.php」のboot()内は、migrateする時も呼ばれてるっぽく(なぜ?)、boot()内とファイル上部でuse宣言をしているところをいったんすべて消せばmigrateもエラーなく動きました。

「□□ServiceProvider.php」のboot()を実際にサービスが動いてるときのみ動作する方法もあるのかもしれませんが、開発中の時みたいに頻繁にデータベースを消したり作り直したりしてる時は、share()もめんどくさい部分があるんだなー、と感じました。

更に追記:ミグレーションでエラーを防ぐ方法

上記、migrate時にエラーが出ちゃう、なんですが、解決方法がありました。

例:カテゴリ一覧を共通変数として持たせる場合

    	if (Schema::hasTable('categories')) {
        	View::share('categories', Category::all());
        }

※Schemaは通常useされてないので、ファイル冒頭で「use Illuminate\Support\Facades\Schema;」を追加しておくコト!

これでテーブルが実際に存在していた時のみSQLを発行するようになるのでmigrate前でもエラーが出なくなります。

【おしらせ、というか完全なる宣伝】

文体がもうぜんぜん適当すぎてあれだけどものすごい自由に書いてるブログ「檸檬だくだく」もよろしく.寝る前に読める恐ろしくくだらないやつです.

こんなにも一ミリも目を引かれないタイトルを取り扱ってます: ココア20g / ハイチュウとかってさ / なぜ米と小麦を食べようと思ったのかの謎 /