LaravelでViewCreatorを使ってPC/SPのビューを切り替える

## 概要
レスポンシブ対応できない状況があるが、同じURLでPC/SPのビューを切り替えたいといった場合の対応方法をメモしておきます。

## 環境
Laravel 5.4
※ 5.3以下では動作確認していないのでエラーが出る場合は適時修正が必要かも？

## やり方の検討

1. 各ビューを呼び出す部分で調整するような仕組みを作る
2. ミドルウェアでビューの検索もとのパスを切り替える
3. 各ビュー作成時に対応する

### 1. 各ビューを呼び出す部分で調整するような仕組みを作る
やり方を変えたい場合に、呼び出し方法自体が変わった時には
全てのコントロールのViewの呼び出し箇所の修正が必要になりそうなので却下。

### 2. ミドルウェアでビューの検索対象フォルダのパスを切り替える
viewsフォルダが一番上の階層からきっちり別れる。
明確にPS/SPのビューが一番上の階層から別れていて見やすい
フォルダのパスは複数定義でき、優先順位の設定が可能なので、
スマホになければPCと行ったようにスマホのテンプレートが無くてもエラーにならない。
ただし、フォルダが完全に別なのでSPのビューを作り忘れが発生しやすい。

### 3. 各ビュー作成時に対応する
各ビューを作成時（includeなどのビューも含む）に実行されるので
実行回数が多くなることが気になる

ただし、以下のような細かな調整ができそう。

・フォルダごと階層を分けず、ファイル名で分ける (例： pc -> index.blade.php, sp -> index_sp.blade.php)
・レスポンシブ対応ページの混在(スマホのビューを作らない場合、PCが読み込む)
・スマホのビュー作成を強制させるよう500番エラーにすることも可能
・PCのみのページを作る場合は、スマホは404 not foundにすることも可能

以上のことから③のやり方でやってみる。

## 構築方法

Laravelのビューには、composerとcreatorという機能があるようです。

実行タイミングが違うようですね。

- creator: viewインスタンスが作成されるとき 
- composer: viewインスタンスからviewがコンパイル、結合されて文字列に変換されるとき

ビュー自体を切り替えるのでcreatorの方が良さそうです。

ということで、creatorで作ります。

## エージェントの判定

スマホかどうかの判定が必要なので、下記ライブラリを導入する。

https://github.com/jenssegers/Laravel-Agent

```
$ composer require jenssegers/agent
```

Laravel 5.4のため、下記の設置ではproviderの方がエラーとなった。
※　原因 ： share()メソッドが5.4ではなくなっているみたい・・・

```php:config/app.php
    'providers' => [
        'Jenssegers\Agent\AgentServiceProvider', // <= 追記
    ],
    'aliases' => [
        'Agent' => 'Jenssegers\Agent\Facades\Agent',  // <= 追記
    ],
```

そのため、providerについてはこちらで上書きする。

```php:app/Providers/AgentServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider,
    Jenssegers\Agent\Agent;

class AgentServiceProvider extends ServiceProvider
{
    /**
     * プロバイダのローディングを遅延させるかどうか
     *
     * @var bool
     */
    protected $defer = false;

    /**
     * 全アプリケーションサービスの初期起動処理
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * コンテナへの結合登録
     *
     * @return  void
     */
    public function register()
    {
        $this->app->singleton('agent', function ($app) {
            return new Agent($app['request']->server->all());
        });
    }
}
```

動作確認

```php:app/Http/Controllers/TopController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Agent;

class TopController extends Controller
{
    /**
     * TOPページ
     *
     * URI : GET /
     *
     * @access  public
     * @return  string
     */
    public function index()
    {
        // サービスプロバイダ経由
        $agent = app('agent');
        var_dump($agent->isMobile());

        // ファサード経由
        var_dump(Agent::isMobile());
    }
}
```

## ViewCreator作成

「app/Http/ViewCreators/」フォルダを作って下記クラスを配置する。

```php:app/Http/ViewCreators/ViewSwitchCreator.php
<?php

namespace App\Http\ViewCreators;

use Illuminate\View\View,
    Illuminate\Support\Facades\View as V,
    Illuminate\View\ViewName,
    Illuminate\View\FileViewFinder,
    Agent;

/**
 * スマホのビューに自動で切り替える
 *
 * @package App\Http\ViewCreators
 */
class ViewSwitchCreator
{
    /**
     * ビュー差し替え対象外とするフォルダ
     *
     * @var array
     */
    private $excluded_folder = ['layouts', 'shared'];

    /**
     * ビュー名の接尾語
     *
     * @var string
     */
    private $view_name_suffix = '_sp';

    /**
     * 新しいプロフィールコンポーザの生成
     */
    public function __construct()
    {
        //
    }

    /**
     * データをビューと結合
     *
     * @param  View  $view
     * @return void
     */
    public function create(View $view)
    {
        // スマホではない場合はスキップ
        if (Agent::isMobile() !== true) {
            return;
        }

        // スマホ向けのビューの場合はスキップ
        $name = $view->getName();
        if ($name && preg_match('|'.$this->view_name_suffix.'$|', $name) !== 0) {
            return;
        }

        // 除外フォルダの場合はスキップ
        list($dir) = explode('.', $name);
        if (in_array($dir, $this->excluded_folder)) {
            return;
        }

        // ビューを変更する
        $name = $name.$this->view_name_suffix;

        // ビューの存在チェック
        // ※ レスポンシブ対応ページも作れるように500エラーにはしない
        if (v::exists($name)) {
            $finder = v::getFinder();
            $view->setPath(
                $finder->find(
                    ViewName::normalize($name)
                )
            );
        }
    }
}
```

## ViewCreator登録

サービスプロバイダを使ってViewCreatorを全てのビューに適用されるよう登録する。

```php:app/Providers/ViewCreatorServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\View,
    Illuminate\Support\ServiceProvider,
    App\Http\ViewCreators as V;

class ViewCreatorServiceProvider extends ServiceProvider
{
    /**
     * 全アプリケーションサービスの初期起動処理
     *
     * @return void
     */
    public function boot()
    {
        View::creator('*', V\ViewSwitchCreator::class);
    }

    /**
     * コンテナ結合の登録
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```
app.phpにプロバイダを登録する。

```php:config/app.php
    'providers' => [
        App\Providers\ViewCreatorServiceProvider::class, // <= 追記
    ],
```

## ビューファイル作成

resources/views/フォルダ配下にファイルをそれぞれVIEWファイルを作成する。

```
┗ resources
   ┣ assets
   ┣ lang
   ┗ views
      ┣ errors
      ┣ layouts　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// このフォルダは差し替え対象外 (レイアウト)
      ┃  ┣ app.blade.php
      ┃  ┗ app_sp.blade.php
      ┣ shared　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// このフォルダは差し替え対象外 (各共有パーツ)
      ┃  ┣ header.blade.php
      ┃  ┣ header_sp.blade.php
      ┃  ┣ footer.blade.php
      ┃  ┗ footer_sp.blade.php
      ┗ top
         ┣ index.blade.php
         ┗ index_sp.blade.php
```

動作確認してみる。

```php:app/Http/Controllers/TopController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Agent;

class TopController extends Controller
{
    /**
     * TOPページ
     *
     * URI : GET /
     *
     * @access  public
     * @return  string
     */
    public function index()
    {
        return view('top.index');
    }
}
```

※ スマホ向けのビューを作らなければ、自動的にPC向けのビューが表示される （レスポンシブ対応ページのため）

以上です。

## 参考サイト
- [laravel5.3でURLを変更せずにスマホとPCを読み分けてそれぞれのviewsを読み込む方法](https://teratail.com/questions/48272)
- [ミドルウェアを使ってビューの探索パスを変更する](http://qiita.com/sogawa@github/items/644710df8d58d54f2665)