Flattenを使ってページキャッシュする

Flatten
https://github.com/Anahkiasen/flatten/

Webサイトを作っているとどうしても気になるのがレスポンス速度。
認証だったり、DBから情報をひっぱったり、外部のAPIを叩いていたりと
最終的なHTMLを構成し吐き出すまでに様々な処理が挟まることで、イライラするサイトになってしまう事は良くあることです。

ECサイトなんかだと、購買意欲を削がないために0.5秒でも速く動くサイト作りが求められますよね。
そのための手段にページキャッシュがあります。
(ビューキャッシュやエレメントキャッシュと呼ぶことも)

これは**動的ページを一定期間だけ静的ページとして扱ってしまう方法**です。
Flattenを使えば簡単にページキャッシュが利用できます。

# Laravelで使う
Flatten自体はLaravelじゃなくても利用出来ますが、Laravelでとても使いやすくなっています。

今回はLaravelの5系でどう使うのか紹介したいと思います。
## 準備
まずはcomposerでインストール

composer.jsonのrequireに1行追加します。

```json:composer.json
"require": {
    "laravel/framework": "5.1.*",
    << 省略 >>
    "anahkiasen/flatten": "^1.1"
}
```

そしたらcomposer updateしましょう。

```bash:
$ composer update anahkiasen/flatten
```

無事にインストール出来たらLaravelで使いやすくするために設定しましょう。
config/app.phpのprovidersとaiasesへFlattenの設定を追記します。

```php:config/app.php
    'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
        Illuminate\Auth\AuthServiceProvider::class,

        // 省略 //

        /**
         * Flatten
         */
        Flatten\FlattenServiceProvider::class, // この行を追記

    ],
```
```php:config/app.php
    'aliases' => [
        'App'       => Illuminate\Support\Facades\App::class,
        'Artisan'   => Illuminate\Support\Facades\Artisan::class,
        'Auth'      => Illuminate\Support\Facades\Auth::class,
        'Blade'     => Illuminate\Support\Facades\Blade::class,

        // 省略 //

        'Flatten'   => Flatten\Facades\Flatten::class, // この行を追記
    ],
```

そして最後にFlatten用のconfigファイルを作ります。
artisanコマンドでも作れますが僕は直接コピーしちゃいました。

```bash:
$ cp vendor/anahkiasen/flatten/config/flatten.php ./config/
```

使い方はさておきconfigファイルにはこんな項目があります。

```php:config/flatten.php
<?php

return [

    'enabled' => true,

    'ignore' => [],

    'only' => [],

    'blockers' => [],

    'lifetime' => 0,

    'saltshaker' => [],

    'timestamp' => true,

    'minify' => false,

];
```

## Viewでキャッシュしたい範囲を指定する
bladeテンプレートでは `@cache` ~ `@endcache` で囲うだけで、その中をまるっとキャッシュしてくれるようになります。

```php:bladeテンプレート
@cache('greeting', 5) {{-- greetingというkeyで5分キャッシュする --}}
 Hello, Sasakure.
@endcache
```

`@cache`の第1引数でkeyを、第2引数にはキャッシュする時間を指定する。
第2引数を省略した場合はconfig/flatten.phpの lifetime値 が採用される。
単位は **分** となる。
たったコレだけで激重ページがチョッパヤになるのでとても嬉しい。

### 変数の扱いには注意が必要

さきほどのbladeテンプレートがPHPにコンパイルされるとこのようになります。

```php:
<?php echo Flatten\Facades\Flatten::section('greeting', 1, function() { ?>
 Hello, Sasakure.
<?php }); ?>
```

**`@cache` ~ `@endcache` で囲った範囲を無名関数にぶっこんで**キャッシュしているのが分かりますね。
そのため下記のような使い方をするとエラーになります。

```php:bladeテンプレート
<?php $name = 'Sasakure'; ?>
@cache('greeting')
 Hello, {{ $name }}.
@endcache
```
これだと$nameなんて変数は無いよって怒られる。

**そんな時はblade記法は使わずに書きましょう。**

```php:bladeテンプレート
<?php $name = 'Sasakure'; ?>
<?php echo Flatten\Facades\Flatten::section('greeting', 1, function() use ($name) { ?>
 Hello, <?= $name ?>.
<?php }); ?>
```

### キャッシュのされかた
Flatten\Facades\Flatten::section()では $this->app['cache'] を利用しています。
つまりLaravelアプリケーション側のキャッシュ指定に合わせてくれます。
config/cache.phpでRedisを使うように設定していればRedisになるといった具合です。

ちなみにキャッシュのキープレフィックスには 'flatten-section-' が付きます。
(実際には更にアプリ側で指定したプレフィックスが付くはずです。)

## ページ全体をキャッシュする
上述した方法だと、そもそもViewの前段(コントローラーやそこから呼び出す処理)が重い場合には効果が薄いです。
ページ全体をキャッシュしたいからコントローラーとか無視したいわ って場合にはFlattenミドルウェアを使いましょう。

app/Http/Kernel.phpのrouteMiddlewareに1行追加します。

```php:app/Http/Kernel.php
    protected $routeMiddleware = [

        // 省略

        'flatten' => 'Flatten\FlattenMiddleware', // この一行を追加
    ];
```

そうしましたら routesにてこんな感じでFlattenMiddlewareを咬ませす。
URLに応じてキャッシュしたレスポンスを返却してくれるようになります。

```php:app/Http/routes.php
Route::group(['middleware' => 'flatten'], function () {
        Route::get('home', 'HomeController@getIndex');
});
```
この例だとキャッシュがあればHomeControllerは呼ばれもしません。

## 予めキャッシュを作っておく機能まである
Flattenにはクローリング機能があります。
これは `artisan flatten:build` というコマンドを叩く事でFlattenに全ページリクエストさせて**予めキャッシュを作っちゃう機能**です。
config/flatten.phpにて対象URLや除外URLなんかを指定する事も出来ます。

```php:config/flatten/phpにて除外URLの指定例
'ignore' => ['home/.+'],
```

こんな感じで正規表現で指定できます。複数指定も可能です。

デプロイスクリプトにキャッシュクリアコマンドと一緒に仕込んでおくと良いですね。