Help us understand the problem. What is going on with this article?

Laravelでウェブアプリケーションをつくるときのベストプラクティスを探る (4) ビュー編

More than 3 years have passed since last update.

はじめに

このエントリーについて

この記事は「Laravelでウェブアプリケーションをつくるときのベストプラクティスを探る」シリーズの一編です。
他の記事は目次からアクセスしてください。

今回はビューへのデータの渡し方に関するベストプラクティスです。

環境

  • PHP 5.6
  • Laravel 5.3

公式リファレンス

Views - Laravel - The PHP Framework For Web Artisans

詳細

よくあるシナリオ

  1. 全ページ共通のデータを使いたい
  2. 特定の複数ページで共通のデータを使いたい

ヘッダーやフッターに動的なメッセージやデータを表示させたいってことはよくあるケースですが、実装方法にいくつかバリエーションがあるので、どれがベストプラクティスか探っていきたいと思います。

ガイドライン

  • 複数のテンプレートで使用するデータのみ View Composer で登録しましょう
  • 複数のテンプレートで共有するデータには共有していることが分かる名前をつけましょう
  • View Composer を利用する際は、極力クロージャではなく、ViewComposer クラスを使いましょう
  • 特別な理由がない限り、View::creator ではなく、View::composer を使いましょう

余談: 単一のテンプレートへデータを渡す方法

ちなみに、単一のテンプレートにデータを渡すには、公式ドキュメントにもあるように

SomeController.php
    return view('some')->with('key', 'value');

のように渡しますが、いくつかバリエーションがあるので、いちおう紹介しておきます。

view('some')->with('key', 'value');
view('some')->with(['key' => 'value']);
view('some', ['key' => 'value']);

これはどのやり方でも特に問題はなさそうなので、統一されていればいいのかな、と思います。

個人的には、コントローラーとビューのインピーダンスミスマッチに敏感でいるために、下記の書き方が好きです。

$key = 'value';
view('some')->with(compact('key'));

compact の引数が多くなってくると、集約を使ってコンパクトにした方がいいなって思って設計を変えるようになるので、ややトリッキーではありますが、「コードの臭い」を漂わせやすくしています。

サンプルコード

app\Providers\ViewServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use View;

class ViewServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::composer('layouts.app', 'App\Http\ViewComposers\AppLayoutComposer');
        View::composer('partials.news', 'App\Http\ViewComposers\NewsPartialComposer');
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

ポイントは、View Composer の名前の付け方で、layouts/app.blade.php で使用する共通データをつくる View Composer の名前を AppLayoutComposer とし、partials/news.blade.php (ニュース一覧を複数のページに埋め込みたいときに使う想定です) の名前を NewsPartialComposer としています。

テンプレートの名前と View Composer の名前を対応付けることによって、どの View Composer がどのテンプレートにデータを供給しているのかが分かりやすくなります。

全ページで共有したい場合は第一引数に '*' と指定すればOKです。この場合も、View Composer の名前はそれと分かるようにつけておきましょう (AllViewComposer とか、GlobalViewComposer とか)。

View::composer メソッドに似たものとして View::creator メソッドがありますが、両者の違いは、

composer - テンプレートが出力される際に (Illuminate\View\View::render が呼ばれると) 呼ばれる
creator - テンプレートが生成される際に (Illuminate\View\Factory::make が呼ばれると) 呼ばれる

というタイミングの違いがあります。

creator を使う条件としては、下記のようにコントローラーでビューを生成してからリターンするまでの間に、何かしら処理が必要な場合、ということになるでしょうか。

SomeController.php
$view = view('some');
// この間にView Creator でセットしたデータにアクセスできる
// $view->dataCreatedByViewCreator のように
return $view;

滅多に使うことはなさそうですが、公式ドキュメントには使い分けの方針みたいなものは書いてなかったので、補足がてら書きました。

続いて View Composer のソースです。
何らかの記事や通知の未読件数を取得してビュー変数にセットします。

app/Http/ViewComposers/AppLayoutComposer.php
<?php

namespace App\Http\ViewComposers;

use Illuminate\View\View;
use Auth;

class AppLayoutComposer
{
    public function compose(View $view)
    {
        $user = Auth::user();
        $unreadCount = ($user) ? $user->unreadArticleCount() : null;
        $view->with('appLayout', compact('unreadCount'));
    }
}

ここでもキーをクラス名に合わせて appLayout としています。これで、どの View Composer によってつくられたデータか、分かりやすくなります。

テンプレートはこうなります。

resources/views/layouts/app.blade.php
{{ Auth::user()->name }} <span class="badge">{{ $appLayout['unreadCount'] }}</span> <span class="caret"></span>

NewsPartialComposer も同様です。こちらではコンストラクタインジェクションで対象のモデルオブジェクトを DI しています。

app/Http/ViewComposers/NewsPartialComposer.php
<?php

namespace App\Http\ViewComposers;

use Illuminate\View\View;
use App\News;

class NewsPartialComposer
{
    public function __construct(News $news)
    {
        $this->news = $news;
    }

    public function compose(View $view)
    {
        $latestNews = $this->news->latest(5);
        $view->with('newsPartial', compact('latestNews'));
    }
}

テンプレートはこうなります。

resources/views/partials/news.blade.php
        @foreach ($newsPartial['latestNews'] as $news)
          <li>{{ $news->title }}</li>
        @endforeach

余談2: View::share を使うべきか

ServiceProvider 内で View::share を呼ぶことによって、全ページ共通のデータを登録できますが、個人的には使わない方がいいと思っていて、静的なデータであれば config ヘルパーを使って、動的なデータであれば View Composer を使うべきと考えます。

理由は、共通データというのは、HTML 上の共通パーツ内に現れるものであって、共通パーツごとに View Composer を使って、ビューデータはそこに閉じ込める方が、あれ?このデータどこでセットしてるんだっけ?とならずに済むからです。 config ヘルパーならどこに定義されているか引数を見れば分かるので、静的なデータはコンフィグファイルに書くのがよさそうです。

まとめ

  • 複数のテンプレートで使用するデータのみ View Composer で登録しましょう
  • 複数のテンプレートで共有するデータには共有していることが分かる名前をつけましょう
  • View Composer を利用する際は、極力クロージャではなく、ViewComposer クラスを使いましょう
  • 特別な理由がない限り、View::creator ではなく、View::composer を使いましょう

他にもこんなベストプラクティスがあるよ、などコメントや編集リクエストいただけると助かります :bow:

nunulk
PHP, Laravel, オブジェクト指向プログラミング, デザインパターン, リファクタリング, 関数プログラミング, etc.
http://nunulk.hatenablog.com
phper-oop
ペチオブはオブジェクト指向ワーキンググループです。様々なエンジニアの方に参加頂いております。
https://phper-oop.connpass.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした