Laravelで405や419エラーに遭遇した場合の対処法

最近、Laravel の勉強をしていて遭遇したエラーをまとめておきます。うっかりミスでも、情報がないと、意外と原因に気づくのに時間がかかるもので・・・。

# まとめ
- HTTP ステータスコードが **405** の場合
    - ルーティングの定義漏れ
- HTTP ステータスコードが **419** の場合
    - CSRF 対策漏れ

# 前提
- Laravel 5.7

# 405
- 別名 「MethodNotAllowedHttpException」

## 事象
例えば、`routes/web.php` で、`/auth/login` という URL に対して、以下のように定義されている場合。

```php:routes/web.php
// GET メソッドは定義済
Route::get('/auth/login', 'LoginController@show');

// POST メソッドは未定義
// Route::post('/auth/login', 'LoginController@authenticate');
```

この場合に、POST メソッドにアクセスすると、405 エラーが発生します。[^1]

[^1]: ちなみに、GET メソッドも定義されていない（`/auth/login` に対して全く定義されていない）場合は、みんな大好き、404 エラーが発生します。

<img width="1261" alt="スクリーンショット 2019-01-02 18.53.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/141299/50752c30-65d8-3dcd-a333-3305491e3494.png">


## 解決策
ルーティングを正しく定義しましょう。

```php:routes/web.php
// GET メソッドは定義済
Route::get('/auth/login', 'LoginController@show');

// POST メソッドも定義する
Route::post('/auth/login', 'LoginController@authenticate');
```

# 419

## 事象
例えば、Blade テンプレートで、以下のようなログインフォームを作成した場合。

```html
<form action="/auth/login" method="post">
    <div class="form-group">
        <label for="email">メールアドレス</label>
        <input type="email" name="email" class="form-control" value="{{ old('email') }}">
    </div>
    <div class="form-group">
        <label for="password">パスワード</label>
        <input type="password" name="password" class="form-control">
    </div>
    <button type="submit" class="btn btn-primary">ログイン</button>
</form>
```

この場合に、「ログイン」ボタンを押すと、419 エラーが発生します。

<img width="1271" alt="スクリーンショット 2019-01-02 18.55.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/141299/e3d34a84-dbe7-f4f2-bba5-3a6057c54dd0.png">


## 解決策
Blade テンプレートに [CSRF 保護](https://readouble.com/laravel/5.7/ja/csrf.html) を追加します。
具体的には、`@csrf` を追加するだけです。

```html
   <form action="/auth/login" method="post">
       <!-- CSRF保護 -->
       @csrf
       
        <div class="form-group">
            <label for="email">メールアドレス</label>
            <input type="email" name="email" class="form-control" value="{{ old('email') }}">
        </div>
        <div class="form-group">
            <label for="password">パスワード</label>
            <input type="password" name="password" class="form-control">
        </div>
        <button type="submit" class="btn btn-primary">ログイン</button>
    </form>
```

## 与太話
`@csrf` が追加されたのは Laravel 5.6 からです。5.5 以前は `{{ csrf_field() }}` でした。5.6 以降でも `{{ csrf_field() }}` も使えるようですが、個人的には `@csrf` のほうが覚えやすくていいかなと思います。

また、同様に、PUT・PATCH・DELETE メソッドを使う際に記述していた `{{ method_field('PUT') }}` も、`@method('put')` が追加されています。

# あとがき
公式ドキュメントに一言書いてあると嬉しかったですね。<del>いや、これぐらい、すぐ分かれよ（そもそも、そんなミスするなよ）という話なのかもしれませんが。</del>

# 参考
- 405 エラー
    - [Laravelエラー405 (Method Not Allowed) の解決策](http://soudan.hatenablog.jp/entry/laravel-error405)
- `@csrf`
    - [New Blade Directives Coming to Laravel 5.6](https://laravel-news.com/new-blade-directives-laravel-5-6)

# おまけ
どうやって POST メソッドをテストすればいいんだろう？ と少し悩んでしまいました。
最初は `Request` のモックを作るのかなと思いきや、[公式ドキュメント](https://readouble.com/laravel/5.7/ja/mocking.html) にちゃんと注意書きがありました。

> Note: Requestファサードをモックしてはいけません。代わりに、テスト実行時はgetやpostのようなHTTPヘルパメソッドへ、望む入力を引数として渡してください。

例えば、「POST メソッドで情報を送信し、正しくリダイレクトされたか？」を確かめたい場合は、以下のように `post()` を使えば良いらしいです。

```php:LoginControllerTest
class LoginControllerTest extends TestCase
{
    // テストで入れたデータはテスト後に削除する
    use RefreshDatabase;

    /**
     * @test
     */
    public function ログインに成功すること()
    {
        // ユーザーを1人作成する
        $users = factory(\App\User::class, 1)->create();

        // POSTで送信したい情報を第2引数に渡す
        $response = $this->post('/auth/login', [
            'email'     => $users[0]->email,
            'password'  => 'secret',
        ]);

        // リダイレクトが成功したかを確認する
        $response->assertRedirect('/');
    }
}
```

ちなみに、これを実行して 405 エラーが出ると、こんな感じになります。

<img width="697" alt="スクリーンショット 2019-01-02 18.58.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/141299/b9416cef-5682-6931-82a9-a6bac466f289.png">