[メモ] Laravel5 でPC/スマートフォンサイトの振り分けを行う


あるサイトにて，スマートフォン／タブレットからのアクセスについてはLaravelのルート自体を変更し対応したい，という要件がありました．
当初，Apacheのmod_rewriteで楽勝，と思っていたんですが，どハマりしてしまい;;

カッとなってLaravel5のMiddlewareで実装してみました．後悔はしていない．

**2015/10/26 追記**

- Middlewareを有効化する部分について書き忘れていました・・ご指摘ありがとうございますm(_ _)m


## Middleware とは ##

HTTP Request に対して実行される小さなプログラムです．
CSRF対策のトークンチェック，ユーザ認証チェックなど，リクエストの全般に対して処理がしたい，なんて時に便利(ということがわかりました，今回)．

## リダイレクト内容 ##

スマートフォン／タブレットからのアクセスの場合

* トップページ   →  /sptop
* その他のページ →  /sp/元々のURLパス

とします．リダイレクト先もLaravelの守備範囲になります．


## Middlewareを書く ##

ファイルの置き場所は app/Http/Middleware 配下になります．
中身はこんな感じ．

```app/Http/Middleware/RedirectIfSmartphone.php
<?php

namespace App\Http\Middleware;

use Illuminate\Support\Arr;
use Illuminate\Routing\Redirector;

class RedirectIfSmartphone {

    /**
     * The USER_AGENTs element.
     *
     * @var array
     */
    protected $agents = [
        "Android",
        "iPhone",
        "iPad", 
        "googlebot-mobile",
        "iemobile",
        "opera mobile",
    ];

    public function handle($request, \Closure $next) {
        
        if (!$this->isSmartphonePage($request) &&
            $this->isSmartphone($request)) {
            return redirect($this->generateSmartphonePage($request));
        }       

        return $next($request);
    }

    protected function isSmartphonePage($request) {
        return preg_match("#^(sptop|sp/)#", $request->path());
    }

    protected function isSmartphone($request) {
        return preg_match("/(". implode("|", $this->agents) .")/",
                        Arr::get($_SERVER, 'HTTP_USER_AGENT', "PC")); 
    }

    protected function generateSmartphonePage($request) {
        $path = $request->path();
        if (empty($path) || $path == "/") {
            return "sptop";
        }       

        return "sp/". $path;
    }
}
```

1. isSmartPhonePage で，現在アクセスしようとしているのがスマートフォン用ページかどうか判断し，
2. USER_AGENTからスマートフォン／タブレットかを判断し
3. リダイレクトすべきURLを生成し
4. リダイレクト．

(1)と(2)の条件判断がFALSEであれば，PCからのアクセスまたはスマートフォン用ページを閲覧中，と判断し，そのまま本来の処理を続行させる，という感じです．

## Middlewareを有効化する ##

クラスファイルを設置しただけではまだ機能しません．Laravelにその存在を知らしめる必要があります．
app/Http/Kernel.php にて，HTTPアクセスに対するMiddleware，ルーティングに関する Route Middleware の定義を行います．

具体的には Kernel.php 内の protected プロパティである $middleware に，処理すべきクラスを列挙していきます．"stack" とコメントにある通り，この順番で実行されていくようなので，私の場合はメンテナンスモードチェックの直後に実行されるよう，2番目に追記しました．


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

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \App\Http\Middleware\RedirectIfSmartphone::class,   // <== ここに追記
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
    ];

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    ];
}
```



もし何かのお役に立てば幸いです．