Webサイトでは、複数のページ(ビュー)にて、同じデータを必要とすることが非常によくあります。
取得方法は、設定値を読み出したり、DBから取得したりなど様々でしょう。しかしながら、それらを都度コントローラーにて同じ取得コードを書くのは効率的ではありません。
そんな時のため、LaravelではViewComposerという機能が用意されています。ViewComposerを使えば、データを1箇所で取得・管理できるのでスマートかつ効率的に問題を解決できます。
本ページでは、そんな便利なViewComposerの使い方と注意点についてご紹介したいと思います。
確認環境
- Laravel 10.32.1
- PHP 8.2.11
ViewComposerの概要
ViewComposerを使って何ができるか、まずは概要から書きます。
複数ページで必要なデータ
例えば全ページに検索ボックスがあり、その検索項目をDBから取得する例を考えてみましょう。
以下の様なフルーツのデータが必要なコントローラがあるとします。
<?php
namespace App\Http\Controllers;
use App\Models\Fluit;
use Illuminate\Http\Request;
class FluitController extends Controller
{
public function index()
{
// トップページの処理をいろいろ行う
// 複数行の処理
// 終了
$fluits = Fluit::all();
return view('web.fluits.index', ['fluits' => $fluits]);
}
public function hoge()
{
// hogeページの処理をいろいろ行う
// 複数行の処理
// 終了
$fluits = Fluit::all();
return view('web.fluits.hoge', ['fluits' => $fluits]);
}
問題
Laravelに慣れないうちはこんなもんか……で済ませてしまいますが、先ほどのコードには以下の問題があります。
- 各ページで同じコード(取得処理)を書く必要がある
- そのコードはページ固有の処理とは無関係
いちいち毎回書くのは面倒ですし、そのページのロジックとは切り離して考えたいですよね。
できるだけコントローラーはすっきりと見通しよく書けるのがベスト。無駄なことに頭を使わないようにしたいものです。
ViewComposerで解決
そこでViewComposerの登場です。
使い方の詳細は後ほど詳しく書きますが、以下のような感じで、任意のビューに$fluits
のデータを渡すことができます。
<?php
namespace App\View\Composers;
use Illuminate\View\View;
use App\Models\Fluit;
class FluitComposer
{
public function compose(View $view)
{
// 全てのFluitデータを取得してビューに渡す
$fluits = Fluit::all();
$view->with('fluits', $fluits);
}
}
実際には、どのビューでこの処理を適用するかを自分で指定する必要があります。
そういった細かい点も含め、これから見ていきましょう。
ViewComposerの使い方【専用ファイル作成編】
ViewComposerを使う方法には、大きく分けて以下の2種類の選択肢があります。
- 専用ファイルを作成する方法
app/Providers/AppServiceProvider.php
に直書きする方法
まずは1の方から見ていきます。
ViewComposerファイルを作成
ViewComposerのファイルは、好きな場所に置いてOKです。決まった場所に置くというルールはありません。
……が、Laravel公式マニュアルの通り、app/View/Composers
にファイルを作れば間違いないでしょう。
ここでは上記ディレクトリに、FluitComposer.php
を新規で作成しました。
ファイルを編集
作ったファイルを、以下の様な感じにします。冒頭で挙げたコードとほぼ同じです。
具体的には、compose
メソッドの中に処理を書き、$view->with
メソッドで、ビューに渡したい変数を指定します。
<?php
namespace App\View\Composers;
use Illuminate\View\View;
use App\Models\Fluit;
class FluitComposer
{
/**
* Bind data to the view.
*
* @param \Illuminate\View\View $view
* @return void
*/
public function compose(View $view)
{
// 全てのFluitデータを取得してビューに渡す
$fluits = Fluit::all();
$view->with('fluits', $fluits);
}
}
ここでは$fluits
という変数を、ビューで使いたい場合を想定しています。
複数の変数を渡すときは、以下の様に$view->with
メソッドに配列で渡すことも可能です。
$view->with([
'fluits' => $fluits,
'season' => $season,
]);
複数回呼ぶこともできます。以下は一つ前の指定と同じ結果となりますね。
$view->with('fluits', $fluits);
$view->with('season', $season);
対象を指定・登録する
これだけではまだViewComposerは使われません。Laravelに使ってもらえるように設定する必要があります。
自分で独自サービスプロバイダを作って登録もできるようですが、app/Providers/AppServiceProvider.php
のboot
メソッド内から登録するのが簡単です。
View::composer
メソッドを使うことで、対象を指定して登録できます。
例えば以下のコードでは、web.fluits.index
ビューを対象に、FluitComposer
の処理が適用されるようにしています。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\View\Composers\FluitComposer;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot()
{
// web.fluits.indexでのみ `FluitComposer` を適用
View::composer('web.fluits.index', FluitComposer::class);
}
指定方法は、普段コントローラーで指定するビューと同様で、パスを.
で区切ります。
例えばweb.fluits.index
という指定は、/resources/views/web/fluits/index.blade.php
にあるBladeビューファイルを意味します。
この指定は、/resources/views/
を最上位とアクセスできるなら、直接ページ表示に紐付くビューファイルに限りません。
コンポーネント等にも指定でき、例えば以下なら/resources/views/components/test.blade.php
を対象に指定可能です。
View::composer('components.test', TestComposer::class);
対象を複数指定する
現実問題として、1つの対象だけを指定することは、ViewComposerの性質上あまりないかもしれません。
複数の対象を指定したい場合は、以下の様に*
(アスタリスク)を含む指定で実現可能です。
public function boot()
{
// web.fluits.以下でのみ `FluitComposer` を適用
View::composer('web.fluits.*', FluitComposer::class);
// ↓おまけ:以下のような使い方もできます。
// web.以下全てに `FluitComposer` を適用
// View::composer('web.*', FluitComposer::class);
// 全てに`FluitComposer` を適用(お勧めしません)
// View::composer('*', FluitComposer::class);
}
*
はワイルドカード=特別な指定で、全指定を意味します。
コメントに書いているように、任意の階層以下を全指定することも可能です。
ただし、*
とだけすると対象が広すぎるので、しない方が良さそうです。これについては後述します。
ViewComposerの使い方【AppServiceProvider直書き編】
もう一つの使い方として、app/Providers/AppServiceProvider.php
のboot
メソッド内に直書きする方法があります。
View::Compowser
メソッドの第2引数にクロージャとして処理を渡してあげればOK。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\View\Composers\FluitComposer;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
View::composer('web.*', function ($view) {
// 全てのFluitデータを取得してビューに渡す
$fluits = Fluit::all();
$view->with('fluits', $fluits);
});
}
指定方法は、最初の方法とほぼ代わりありません。
この場合、新たにファイルを作る必要もないのであっという間に実装可能です。
ただしAppServiceProvider.php
はあれこれ書くと肥大するので、複雑な処理の場合は向かないと思います。この例のように、ごく簡単な場合にはありかもしれません。
ViewComposerの問題・気をつけるポイント
このように設定しておくだけで、対象のBladeファイルから自由に変数を参照できます。
便利ですよね、私もこれを知ってからは手放しで使っていました。
しかし、大きな落とし穴もあるので要注意です。仕様を理解していないと、残念なコードを納品してしまいます(私も納品後に気づいたことがあります)。
対象ファイルの読み込み時、「何度でも」呼ばれる
「ViewComposerの変数は、複数のビューで共通で使える」とだけ解説してあるサイトが多いです。
それは間違ってはいないかもしれませんが、確実に落とし穴にはまります。
実は、ViewComposerが呼ばれるのは1度だけとは限りません。指定する対象にあてはまるファイルの読み込み時、何度でも呼ばれます。
例えば以下のような外部ファイルを読み込むことは普通にあると思います。
- @extends
- @include
- コンポーネント
これらがView::compser
メソッドの第1引数で指定したディレクトリ内に配置されていれば、都度ViewComposerの処理は実行されます。
「どのBladeファイルの中でも確実にViewComponentで定義した変数を使わせてあげましょう」ということなのでしょうけど、私には想定外でした。
Bladeを使っていて、テンプレート目的のコンポーネント(または@extends)を使わないことって、あまりないですよね!?コンポーネントは何十個も使うこともめずらしくありません。
ですから、この仕様はきちんと理解しておく必要があります。
*の指定に注意
特に問題が生じやすいのは、*
が含まれる指定です。
ViewComposerの必要とされるシーンからして、*
を使った解説は非常に多いです。Laravel公式マニュアルですら、さらっと「*
指定すれば全部のビューでアタッチされるよ」という感じで説明しています。
The composer method also accepts the * character as a wildcard, allowing you to attach a composer to all views:
引用: Views – Laravel 11.x – The PHP Framework For Web Artisans
ひいては「全ページで使う変数があるから*
だよね!」と、安易に以下の様な指定をしてしまいがちです。
public function boot()
{
// 全てのViewで`FluitComposer` を適用(お勧めしません)
View::composer('*', FluitComposer::class);
}
しかしこれは/resources/views/
全てに適合してしまいます。
例えば/resources/views/web/index.blade.php
にて、testコンポーネントを読み込んだとしましょう。
testコンポーネントは/resources/views/components/
に配置するので、testコンポーネント読み込み時もViewComposerの処理が再度実行されます。
もしViewComposerでDBアクセスがあれば、再度SQLが発行されてしまう……。例え表面的な害は無くとも、これは意図しませんよね。
*
は影響が大きすぎるので、基本的に一切使わない方が良さそうです。
開発ツール上で、DBアクセスが100を超えていたことがありびっくりしました。本番ではキャッシュしてはいましたが、無駄な処理にヒヤリとしました。
キャッシュ
ViewComposerは複数ページで呼ばれることが多いです。
もしもDBアクセスやネットワーク処理が入る場合は、キャッシュをしておくと速度や負荷が改善されることがあります。
<?php
namespace App\View\Composers;
use Illuminate\View\View;
use App\Models\Fluit;
use Illuminate\Support\Facades\Cache;
class FluitComposer
{
public function compose(View $view)
{
// キャッシュされたデータがなければデータベースから取得し、キャッシュに保存
$fluits = Cache::remember('fluits', 60, function () {
return Fluit::all();
});
$view->with('fluits', $fluits);
}
}
この場合、60秒はDBへのアクセスを行いません。
Laravelは便利ですが、こういった細かいことを気にかけてあげると、無駄なリソースを使わず、より高速なWebアプリケーションを構築することが可能です。
複数人数での利用について
ViewComposerから取得する変数は、通常の方法(コントローラー)とは異なるところに取得ロジックを書きます。そのため、複数人数で開発する場合は注意が必要です。
例えばViewComposerを知らない人がいれば間違いなく混乱するでしょう。
設計思想的に、利用が好まれないケースもあるかもしれません。
また、クラスに分ける方法や、AppServiceProviderに直書きできるなど、使い方にも柔軟性があります。クラスに分けた場合の配置場所も自由ですしね。
私のようなフリーランスエンジニアは比較的好きに決められるので問題は起こりづらいです。実際、便利に使ってます。
しかし複数人で開発する場合は、チームのルールに則って使用することが大切と思います。
余談:ViewCreator
ViewComposerに似たLaravelの機能として、ViewCreatorというものがあります。
クラスの置き場所や名称を以下の様に変えただけのものです。
Composer
→Creator
View::composer
→View::creator
compose
→create
こんな感じです。
<?php
namespace App\View\Creators;
use Illuminate\View\View;
use App\Models\Fluit;
class FluitCreator
{
/**
* Bind data to the view.
*
* @param \Illuminate\View\View $view
* @return void
*/
public function create(View $view)
{
// 全てのFluitデータを取得してビューに渡す
$fluits = Fluit::all();
$view->with('fluits', $fluits);
}
}
兄弟のように瓜二つですね。この例の場合、実行結果も全く同じです。
違うのは実行されるタイミング。
ViewCompose
はビューがレンダリングされる時に実行ViewCreator
はビューがインスタンス化された直後に実行
できれば直前まで実行してほしくはないので、個人的にはあまり使う機会はまだありません。
ですが、記憶のすみに覚えておくと、何かに使えることもあるかもしれませんね。
まとめ
LaravelのViewComposerは非常に便利で簡単に使えます。しかし、意外と*
で使ってしまったりしている方も多いのではないかな、と思います。
きちんと仕様を理解しておかないと、とんでもなく無駄な処理を繰り返してしまうことがありますが、なかなか気がつきにくいんですよね。
逆に言えば、理解して使用すればコントローラーもスッキリし、ファットコントローラー化の要因を一つ減らしてくれます。エンジニアの考えるリソースを増やしてくれるので、とてもありがたい機能です。
仕様の落とし穴的なことについては、改めて別ページで詳細に書きたいと思います。
コメント