🏃

[Laravel] ViewComposer でコントローラをすっきりさせる

コードの保守性を高めるためのちょっとしたテクニック。

ViewComposer ってなに?

View に変数を結合するためのしくみです。 普通ならコントローラの各メソッドで view()->with(compact('user')) のようにして渡すところを、別の場所で渡すことができます。 たとえば、以下のように変数を渡す処理をコントローラから削除できます。

public function index()
{
    $users = User::all();
    $count = $users->count();

    return view('user.index')->with(compact('users', 'count'));
}

// ViewComposer を使って $count を別の場所で定義した
public function index()
{
    $users = User::all();

    return view('user.index')->with(compact('users'));
}

2 つ目のコードには $count が定義されていませんが、ViewCompoesr によって定義されているので、user.index のなかでちゃんと使うことができます。 ちなみに、結合処理が実行される (View と変数が結合される) のは、View が render されるときです。

何がうれしいの?

メリットはおもに 2 つ。

1 つはコントローラがすっきりすること。 たとえば、次のコードでは $count が複数箇所に現れていて冗長ですが、

public function index()
{
    $users = User::all()
    $count = $users->count();
    return view('user.index')->with(compact('users', 'count'));
}

public function show(User $user)
{
    $count = User::all()->count();
    return view('user.show')->with(compact('user', 'count'));
}

$count を渡す処理を ViewComposer で書けば、以下のようにすっきりします。

public function index()
{
    return view('user.index')->with(['users' => User::all()]);
}

public function show(User $user)
{
    return view('user.show')->with(compact('user'));
}

もう 1 つは、1 箇所にまとまっていて修正しやすいこと。

ViewComposer は複数箇所に定義されている同じ変数を 1 箇所にまとめることができます。 先の例では $count の出現箇所はたった 2 箇所でしたが、これが複数のコントローラをまたいで数 10 箇所にもなったら、変更するのが非常に大変です。 それに、変更漏れがあればバグを引き起こしかねません。 その点、ViewComposer で View と変数の結合を一括管理しておけば、その心配はなくなります。

どうやって作るの?

1. ServiceProvider を作る

php artisan make:provider ViewServiceProvider

App\Providers\ViewServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class ViewServiceProvider extends ServiceProvider
{
    public function boot()
    {
        //
    }

    public function register()
    {
        //
    }
}

config への追加も忘れずに。

config/app.php

    'providers' => [
        ...
+       App\Providers\ViewServiceProvider::class,
    ],

2. Composer を作る

特にコマンドは用意されていないので、手動でファイルを作ります。

App\Http\View\Composers\UserComposer.php

<?php

namespace App\Http\View\Composers;

use App\User;
use Illuminate\View\View;

class UserComposer
{
    protected $users;

    public function __construct()
    {
        $this->users = User::all();
    }

    public function compose(View $view)
    {
        $view->with('count', $this->users->count());
    }
}

3. 表示してみる

app\Providers\ViewServiceProvider.php

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

app\resources\views\welcome.blade.php

-   <title>Laravel</title>
+   <title>Laravel {{ $count }}</title>

ここまでくれば、welcome ページで $count が使えるはず。

resources\views\user\index.blade.php で使いたいときは、以下のようにします。

public function boot()
{
    View::composer(
        'user.index', 'App\Http\View\Composers\UserComposer'
    );
}

resources\views\user 配下のすべてのビューに適用したいときは、以下のように書くしかなさそうです。

public function boot()
{
    View::composer(
        ['user.index', 'user.create', 'user.show', 'user.edit'],
        'App\Http\View\Composers\UserComposer'
    );
}

実際には、ヘッダやサイドメニューで使う変数を ViewComposer で定義することが多いので、 header.blade.phpsidemenu.blade.php などに対して変数を結合することになると思います。 ちなみに、その場合は変数の結合を定義した blade 内でしか変数を参照できないことに注意が必要です。 つまり、header.blade.php に結合した変数を、header.blade.php をインポートするような別ファイルのなかで使うことはできないということです。