ミドルウェアを使ってビューの探索パスを変更する

Laravel5のインストール直後に設置されているビューを見たところ、もともとレスポンシブな感じではあるけど、PCとモバイルで表示要素が大きく違う画面はビューファイルが同じだとツライわ−ってことがあると思うんです。

なので、ミドルウェアを使ってビューファイルの探索パスを変更する方法を調べてみました。


## パッケージのインストール

ググると、なんだか良さげなパッケージがあったのでこれを使いたいと思います。
https://github.com/jenssegers/Laravel-Agent

READMEに従ってcomposerでインストールした後、config/app.phpの'providers'と'aliases'に追記します。

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

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

	'aliases' => [
		....
		
		'Agent'     => 'Jenssegers\Agent\Facades\Agent',  // <= 追記
	],
```

## ミドルウェアの作成

まずは config/view.phpにモバイル向けのビューディレクトリを追記しておきます。

```php:config/view.php
    ....
    
    'compiled' => realpath(storage_path().'/framework/views'),
    'mobile_path' => realpath(base_path('resources/mobile_views')), // <= 追記
```

続いて、適当なミドルウェアを作成します。

```
$ php artisan make:middleware ViewSwitchMiddleware
```

```php:app/Http/Middleware/ViewSwitchMiddleware.php
<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Routing\Middleware;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\View\FileViewFinder;
use Agent;

class ViewSwitchMiddleware implements Middleware {

	/**
	 * The view factory implementation.
	 *
	 * @var \Illuminate\Contracts\View\Factory
	 */
	protected $view;

	/**
	 * Create a new instance.
	 *
	 * @param  \Illuminate\Contracts\View\Factory  $view
	 * @return void
	 */
	public function __construct(ViewFactory $view)
	{
		$this->view = $view;
	}

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		$ua = $request->server->get('HTTP_USER_AGENT');

		if (Agent::isMobile($ua)) {
			$app = app();
			$paths = $app['config']['view.paths'];
			array_unshift($paths, $app['config']['view.mobile_path']);

			$this->view->setFinder(new FileViewFinder($app['files'], $paths));
		}

		return $next($request);
	}
}
```

Agent::isMobile() が true の場合にFinderインスタンスをすげ替えることで、resources/mobile_views/, resources/views/ の順にビューファイルを探してくれるようになりました。

※ FileViewFinderの実装を見てみると、addLocation()は有るのに prependLocation()は無いらしい。
※ `$this->view->getFinder()->prependLocation($mobile_path);` みたいに書ければ良かったんですけどね。

## ビューファイルの用意

上記で指定した resources/mobile_views/以下に適当なビューファイルを作成します。

```
$ mkdir -p resources/mobile_views/

$ cat <<EOF > resources/mobile_views/welcome.blade.php
これはモバイル向け画面ですよ。
<div>使ってるファイル: {{ \$this->getCompiler()->getPath() }}</div>
EOF
```

## ミドルウェアの登録

app/Http/Kernel.phpにグローバルミドルウェアとして登録します。

```php:app/Http/Kernel.php
    protected $middleware = [
        ....
        
        'App\Http\Middleware\ViewSwitchMiddleware', // <= 追記
    ];
```

そしたらWEBブラウザの開発者ツールなどでユーザーエージェントを変更して確認します。

## ユニットテストを書く

最後に、テストがないと同僚がプルリクエストを受け付けてくれないのでユニットテストを書きます。

```php:tests/Http/Middleware/ViewSwitchMiddlewareTest.php
<?php
use App\Http\Middleware\ViewSwitchMiddleware;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\ServerBag;

class ViewSwitchMiddlewareTest extends TestCase {

	public function setUp()
	{
		parent::setUp();
		$this->view = app('view');
		$this->middleware = new ViewSwitchMiddleware($this->view);
		$this->request = new Request();
	}

	public function handleDataProvider()
	{
		return [
			[
			'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36',
			'resources/views'
			],
			[
			'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25',
			'resources/mobile_views'
			],
			[
			'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03S) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19',
			'resources/mobile_views'
			],
		];
	}

	/**
	 * @dataProvider handleDataProvider
	 */
	public function testHandle($ua, $expected)
	{
		$this->request->server = new ServerBag(['HTTP_USER_AGENT' => $ua]);
		$this->middleware->handle($this->request, function($request){});

		$paths = $this->view->getFinder()->getPaths();
		$this->assertEquals(realpath(base_path($expected)), reset($paths));
	}
}
```

phpunitを実行してグリーンになればOKです。

```
$ phpunit tests/Http/Middleware/ViewSwitchMiddlewareTest.php
```