LaravelでAjaxかどうか判定し、自動テストにも対応させる

Laravelにおいて、あるルートはAjaxの場合のみ通したい、
さらにphpunitでAjaxのルートもテストしたい、という状況が発生したため、
その方法を考えてみました。
（確認環境：Laravel5.4）

# Ajaxのみ許可するルート

`\Illuminate\Http\Request`クラスには、`ajax()`というメソッドが用意されています。
そのリクエストがAjax通信か否かを`true``false`で返してくれます。

```php:例
if ($request->ajax()) {
    // Ajaxである！
} else {
    // Ajaxではない
}
```

また、ルートに対して何らかの制限やフィルターをかける場合、[ミドルウェア](https://readouble.com/laravel/5.4/ja/middleware.html)を使うのが良さげです。
ということで、上記メソッドとミドルウェアを使ってAjax通信以外を遮断してみます。

```bash:ミドルウェア追加
php artisan make:middleware CheckAjax
```

```php:app/Http/Middleware/CheckAjax.php　を修正
    public function handle($request, Closure $next)
    {
        // Ajaxアクセスでなければ404
        abort_unless($request->ajax(), 404);

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

```php:app/Http/Kernel.php　ミドルウェア短縮キー追加
    protected $routeMiddleware = [
        // ...

        'ajax' => \App\Http\Middleware\CheckAjax::class, // CheckAjaxミドルウェアの短縮キーを登録

        // ...
    ];
```

```php:routes/we.php　ルートに登録
    Route::get('/only/ajax', 'OnlyController@ajax')->middleware('ajax');
```

これで、`/only/ajax`というルートに関しては、Ajaxによる通信以外は404エラーを返すようになりました。

試しにブラウザで`/only/ajax`にアクセスしてみて、404になれば成功です。

# AjaxなルートをHttpテストする
Laravelには[Httpリクエストをテストする機能](
https://readouble.com/laravel/5.4/ja/http-tests.html)が存在していますが、
前述のAjaxによる通信制限が入っているルートをテストしようとしても、
普通にやると制限に引っかかって404になってしまいます。

```php
$response = $this->get('/only/ajax');
$response->assertStatus(200); // ajaxと判定されずに404になり、失敗
```

なんとかしてAjaxの通信であると判定させる必要があります。
そこで、まずは`$request->ajax()`がどういう基準で判定しているか、コードを追ってみました。

```php:vendor\laravel\framework\src\Illuminate\Http\Request.php
    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }
```

`isXmlHttpRequest()`というメソッドの別名になってます。
`isXmlHttpRequest()`の方は

```php:vendor\laravel\framework\src\Illuminate\Http\Request.php
    public function isXmlHttpRequest()
    {
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
    }
```

となっており、ヘッダーの`X-Requested-With`の値が`XMLHttpRequest`であれば、
Ajax通信であると判定しているみたいです。

テストコードで使っている`$this->get()`の実装は以下の通りです。

```vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php
    public function get($uri, array $headers = [])
    {
        $server = $this->transformHeadersToServerVars($headers);

        return $this->call('GET', $uri, [], [], [], $server);
    }
```

第一引数が`$uri`、第二引数が`$headers`になってます。
第二引数にリクエストヘッダが指定できるようです。
ということで、第二引数に`X-Requested-With: XMLHttpRequest`をセットしてみます。

```php
$response = $this->get('/only/ajax', ['X-Requested-With' => 'XMLHttpRequest']);
$response->assertStatus(200); // Ajaxと判断され、成功
```

無事、Ajaxとしてリクエストを処理してくれました。

## 共通で使えるようにする

毎回毎回第二引数に`['X-Requested-With' => 'XMLHttpRequest']`と書くのは面倒なので、
共通で使えるようにしてみます。

追加したテストケースは必ず`TestCase`クラスを継承しているようなので、
ここにメソッドを追加してみました。

```php:tests\TestCase.php
abstract class TestCase extends BaseTestCase
{
    // ...

    protected function getAjax($uri, array $headers = [])
    {
        $headers['X-Requested-With'] = 'XMLHttpRequest';
        return $this->get($uri, $headers);
    }
}
```

これを`get()`と置き換えれば、勝手にAjax用のヘッダーを付与してGETリクエストしてくれるようになります。

```php
$response = $this->getAajx('/only/ajax');
$response->assertStatus(200); // 200で成功
```
`get()`の他にも`json()`、`get()`、`post()`、`put()`、`patch()`、`delete()`
といったメソッドが用意されているようなので、それぞれに対してxxAjaxを用意すると良いと思います。

また、今回は基底クラスにメソッドを追加しましたが、
[`trait`](http://php.net/manual/ja/language.oop5.traits.php)とか使ったらもっとスマートになるかもしれません。