Laravel5でメンテナンス画面を実装する

### はじめに

メンテナンスモードを即時実行
resources/views/errors/503.blade.php の内容を表示する

```
php artisan down
```

メンテナンスモードを即時解除

```
php artisan up
```

以下の手順をする際、手違いでメンテナンスモードになったらupしてください

### 要件

* 管理画面でメンテナンス画面に切り替えれるスイッチをつける
* オンにすると全ページで503ページが表示される
* ただし、許可したIPアドレスからのアクセスのみ、オンにしていても全ページが通常通り表示される
* 管理画面でメンテナンス画面に表示するメッセージを入力できる
* 入力した内容はメンテナンス画面で表示される

### 実装

##### 必要ファイルの用意

```
touch resources/views/admin/maintenance/index.blade.php
php artisan make:controller MaintenanceController
php artisan make:model Maintenance
php artisan make:migrate create_maintenances
php artisan make:middleware CheckForMaintenanceMode
```

##### ルーティング

```php:app/Http/routes.php
Route::resource('maintenance', 'MaintenanceController', ['only' => ['index', 'store']]);
```

##### View

※ここではLaravelCollectiveを使用しています。
使用していない場合はふつうにHTMLの書き方でformタグとか書いていけばいいです。
やってるのはラジオボタンとテキストエリアの作成です。

```html+php:resources/views/admin/maintenance/index.blade.php
{{ Form::open(['url' => route('admin.maintenance.store')]) }}
{{ Form::radio('mode', 1, $maintenance->mode, ['class' => 'maintenance', 'id' => 'mode_1']) }}
{{ Form::label('mode_1', 'オン') !!}
{{ Form::radio('mode', 0, !$maintenance->mode, ['class' => 'maintenance', 'id' => 'mode_0']) }}
{{ Form::label('mode_0', 'オフ') !!}
@if($errors -> maintenance -> has('mode'))
{{ $errors -> maintenance -> first('mode') }}
@endif
{{ Form::textarea('body', $maintenance -> body, ['class' => 'maintenance', 'id' => 'body']) }}
@if($errors -> maintenance -> has('body'))
{{ $errors -> maintenance -> first('body') }}
@endif
{{ Form::submit('設定') }}
{{ Form::close() }}
```

普通にLaravelを構築してたら最初から503のViewファイルがあるはずです。なかったら作って

```html+php:resources/views/errors/503.blade.php
<h1>メンテナンス中</h1>
<p>{!! nl2br($maintenance -> body) !!}</p>
```

##### Controller

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

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Maintenance;

class MaintenanceController extends Controller
{
    public function index() {
        if(count(Maintenance::all()) == 0) {
            $maintenance = new Maintenance();
        }
        else {
            $maintenance = Maintenance::orderBy('updated_at','desc')->first();
        }

        $this -> mode();

        return view('admin.maintenance.index')->with(['maintenance' => $maintenance]);
    }

    public function store(Request $request)
    {
        if(count(Maintenance::all()) == 0) {
        	$maintenance = new Maintenance();
        }
        else {
        	$maintenance = Maintenance::orderBy('updated_at','desc')->first();
        }

        $rules = [
            'mode' => 'required|boolean',
            'body' => 'required|string',
        ];

        $validator = \Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            return back()->withErrors($validator,'maintenance');
        }

        $maintenance -> mode = $request -> mode;
        $maintenance -> body = $request -> body;
        $maintenance -> save();
        return redirect()->back();
    }

    public function mode()
    {
        $maintenance = Maintenance::orderBy('updated_at','desc')->first();

        if($maintenance -> mode == 1){
            return \Artisan::call('down');
        }
        else {
            return \Artisan::call('up');
        }
    }
}
```

##### Model

app/Maintenance.php

何もしない

##### Migrate

```php:database/migrations/2016_10_19_100000_create_maintenances.php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateMaintenances extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('maintenances', function($table) {
            $table->increments('id');
            $table->boolean('mode');
            $table->text('body');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('maintenances');
    }
}
```

DBにテーブル情報を登録

```
php artisan migrate
```

##### Middleware

ここを参考にしました。
http://www.larajapan.com/2016/01/10/%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E7%94%BB%E9%9D%A2%E3%81%AE%E8%A3%8F%E5%8F%A3/

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

namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\HttpKernel\Exception\HttpException;

class CheckForMaintenanceMode
{
    /**
     * The application implementation.
     *
     * @var \Illuminate\Contracts\Foundation\Application
     */
    protected $app;
    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
    }
 
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $allow = ['***.***.***.***']; // ここにアクセス許可したいIPアドレスを書く。複数の場合は['hoge','hoge']のように区切る

        if ($this->app->isDownForMaintenance()) {
            if (!in_array($request->getClientIp(), $allow)) {
                throw new HttpException(503);
            }
        }
 
        return $next($request);
    }
}
```

##### Kernel

```php:app/Http/Kernel.php
    protected $middleware = [
        \App\Http\Middleware\CheckForMaintenanceMode::class,
    ];
```

##### 503画面に変数を持たせる

404画面や503画面などの例外処理のページは、Controllerでwithメソッドで
どうこうするみたいなやり方で変数を渡すことができません。（やり方あったら教えてください）

これらの例外処理のページは以下のやり方で変数を持たせることができます。

これを参考にしました。
http://stackoverflow.com/questions/39217513/how-to-pass-variables-to-http-error-layouts-in-laravel

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

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer('errors.503', function($view)
        {
            $maintenance = \App\Maintenance::orderBy('updated_at','desc')->first();

            $view->with(['maintenance' => $maintenance]);
        });
    }

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

### おわり

おわり

### おまけ

上記のやり方でメンテナンス画面の実装は完了しましたが、
許可したIPアドレスでは503ページが表示されない仕様なので、
実際にユーザーから503ページがどのように見えてるかが確認できません。
なので、503ページのプレビュー機能を追加します。

##### ルーティングを修正



```php:app/Http/routes.php
Route::resource('maintenance', 'MaintenanceController', ['only' => ['index', 'store']]);
Route::get('maintenance/preview', function(){
    return view('errors.503');
});
```

##### Viewを修正



これをどっか適当なところに入れてください。これで終わり。簡単ですね

```html:resources/views/admin/maintenance/index.blade.php
<a href="/admin/maintenance/preview" target="_blank">プレビュー</a>
```