【Laravel5.5】ビューディレクトリを分割するよ


# まえがき
マルチログインで作っている際、コントローラを別々に作っていると、
合わせてビューも別ディレクトリに分けて作りたいな、と思った次第。
とりあえず出来たので残しておきます。


# 前提

## ツールのバージョン

- XAMPP v3.2.4
    - PHP 7.3.8
        - Laravel 5.5.45


## すでにやってるルーティング

```php:routes/web.php
<?php
// user
Route::prefix('user')->name('user.')->group(function () {
    // ユーザー画面
    Route::get('/', 'User\TopController@index')->name('top');
});

// admin
Route::prefix('admin')->name('admin.')->group(function () {
    // 管理者画面
    Route::get('/', 'Admin\TopController@index')->name('top');
});
```

URLとルーティング名にプレフィックスを使ってます。
なので、ユーザー側のトップのURLは `http://localhost/user/`、
管理側のトップURLは `http://localhost/admin/`になります。
同様に、ユーザー側トップのルーティング名は `user.top`、管理側は `admin.top`となります。

コントローラはマルチログインごとにディレクトリをきっているので、
コントローラアクションの呼び出しにも含めています。

ただ、このまま素直に作ってしまうと、ビューが同じテンプレート名になってしまうので、
それが嫌だった、というわけです。
まぁ、トップくらいなら `top/user.blade.php` とかで分ければいいだけだけど。。


# 設定
呼び出しやすいように設定ファイルにパスを追加しておきます。

```php:config/view.php
<?php
return [
    /**
     * 各権限ごとのビューテンプレートパス
     */
    'user_path' => resource_path('views/user'),
    'admin_path' => resource_path('views/admin'),
];
```

で、以下のミドルウェアを用意。

```php:app/Http/Middleware/ViewSwitchMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\View\FileViewFinder;

/**
 * 権限により参照するビューディレクトリを変更する
 */
class ViewSwitchMiddleware
{
    /**
     * ビュー
     * @var Illuminate\Contracts\View\Factory
     */
    protected $view;

    public function __construct(ViewFactory $view)
    {
        $this->view = $view;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     * @see https://qiita.com/sogawa@github/items/644710df8d58d54f2665
     */
    public function handle($request, Closure $next)
    {
        $add_path = $this->getViewPath($request);
        if (!empty($add_path)) {
            $this->resettingView($add_path);
        }

        return $next($request);
    }

    /**
     * ビューディレクトリパスを取得
     * ルーティングのプレフィックスにより見分ける
     *
     * @access private
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    private function getViewPath($request)
    {
        // プレフィックスで分岐
        $uri_prefix = $request->route()->getPrefix();
        if ($uri_prefix == '/admin') {
            // 管理
            return \Config::get('view.admin_path');
        } else {
            // ユーザー
            return \Config::get('view.user_path');
        }

        // // 権限で分岐
        // $middlewares = $request->route()->middleware();
        // // ルーティング名で分岐
        // $route_name = $request->route()->getName();
    }

    /**
     * ビューの再設定
     *
     * @access private
     * @param  string  $add_path
     * @return void
     */
    private function resettingView($add_path)
    {
        // 権限ごとのディレクトリを先に検索させるため、パス情報の先頭へ
        $paths = \Config::get('view.paths');
        array_unshift($paths, $add_path);

        // パスを変更するためFileViewFinderを生成
        // ただし、そのまま新しいFileViewFinderをセットするとすでにセットされたhint情報が消えてしまうので
        // すでに設定済みのFileViewFinderのhintをコピーしてからセットする
        // (ページャーや通知でhint情報をいれている)
        $finder = new FileViewFinder(app()['files'], $paths);
        $old_finder = $this->view->getFinder();
        foreach ($old_finder->getHints() as $namespace => $hint) {
            $finder->addNamespace($namespace, $hint);
        }

        $this->view->setFinder($finder);
    }
}
```

ちょうどよくルーティングでプレフィックスを使っているので、
それでもってテンプレートのパス分岐も判定します。

パスが取得できたら、テンプレートの設置先パスの先頭に追加。
viewのFinderを差し替えるわけだけど、
この際にそのまま新しいFinderを入れてしまうと、
元のFinderに入っていたhintがなくなりそもそも表示でコケることになるので、
ここだけ新しいFinderに入れ直してから再セットします。

その後、このMiddlewareを読み込ませて設定終わり。

```php:app/Http/Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    //
    // 省略
    //  

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \App\Http\Middleware\ViewChangeMiddleware::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    //
    // 省略
    //  
}
```

これにより、ビューはそれぞれで分割できるようになります。

```
+---resources
    \---views
        +---admin
        |   \---top
        \---user
            \---top
```

こんな感じ。


# あとがき
やってる途中で、たんにディレクトリ分けて、ビューを呼び出すパス変えてやればいいだけだな、とは思ってた(´・ω・｀)
まぁ、もう作ってたところ書き換えて回るのもメンドいし、いいかな、と。


# 参考
[ミドルウェアを使ってビューの探索パスを変更する](https://qiita.com/sogawa@github/items/644710df8d58d54f2665)