Laravel はじめました [4. 基礎]

※前回の記事は以下です

https://qiita.com/soso555/items/83de4dc95b49a42e85bd

# 概要
- 今回は [基礎](https://readouble.com/laravel/5.7/ja/routing.html) 編

# 基本的なルーティング
- Webページのルーティングは ```routes/web.php``` に以下のように記載しましょう
    - 引数は URI とクロージャ

    ```php
    Route::get('foo', function () {
        return 'Hello World';
    });
    ```

    - 以下のように URI とコントローラ名称、メソッド名称でも定義できるよ

    ```php
    Route::get('/user', 'UserController@index');
    ```

- API のルーティングは ```routes/api.php``` に記載しましょう
    - URI に ```/api``` プレフィックスが自動で付くよ
        - ex) ```http://localhost/api/user```
        - ```App\Providers\RouteServiceProvider``` でプレフィックス付けたりしてるよ

- ルーティング定義に使う ```Route::``` ファサードは HTTP メソッド分用意されているよ

    ```php
    Route::get($uri, $callback);
    Route::post($uri, $callback);
    Route::put($uri, $callback);
    Route::patch($uri, $callback);
    Route::delete($uri, $callback);
    Route::options($uri, $callback);
    ```

    - 複数の HTTP メソッドに登録する場合は
        - ```Route::match(['get', 'post'], '/', function () {});```
    - いずれかの HTTP メソッドに登録する場合は
        - ```Route::any('foo', function () {});```

## リダイレクト
- 一時的なリダイレクト
    - ```Route::redirect('/here', '/there');```
- 恒久的なリダイレクト
    - ```Route:: permanentRedirect('/here', '/there');```

## ビュールート
- ある URI に対して、コントローラではなく単純にビューを返すだけの場合、以下が使える
    - ```Route::view('/welcome', 'welcome', ['name' => 'Taylor']);```
        - 第三引数はビューに渡すパラメータ。省略可能
        - URI ```/welcome``` にアクセスがきたら、ビューファイル welcome を表示しますよ

# ルートパラメーター
## 必須パラメータ
- URI の中にパラメータを定義できる

    ```php
    Route::get('user/{id}', function ($id) {
        return 'User '.$id;
    });
    ```

- 複数も可能

    ```php
    Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
        //
    });
    ```

- ハイフンは使えないよ

## 任意パラメータ
- URI の中に任意パラメータを定義できる
    - デフォルト値は必ず指定すること

    ```php
    Route::get('user/{name?}', function ($name = 'John') {
        return $name;
    });
    ```

## 正規表現制約
- ルートパラメータに正規表現で値の規則を付与できるよ
    - これに違反したらどうなるの？
        - 404 NotFound になった！

    ```php
    Route::get('user/{name}', function ($name) {
        //
    })->where('name', '[A-Za-z]+');
    
    Route::get('user/{id}', function ($id) {
        //
    })->where('id', '[0-9]+');
    
    Route::get('user/{id}/{name}', function ($id, $name) {
        //
    })->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
    ```

- グローバル制約
    - 指定した正規表現でいつもルートパラメータを制約できる
        - RouteServiceProvider@boot の ```parent::boot()``` の前に定義すること

    ```php
    public function boot()
    {
        Route::pattern('id', '[0-9]+');
    
        parent::boot();
    }
    ```

    - 上記の場合、「$id というルートパラメータを使用する全てのルートにて、数値のみ許可する」となる

- スラッシュのエンコード
    - ```where()``` で正規表現を上書きしてやれば、ルートパラメータの値としてスラッシュを使用できる

    ```php
    Route::get('search/{search}', function ($search) {
        return $search;
    })->where('search', '.*');
    ```

## 名前付きルート
- ルート定義にnameメソッドをチェーンすることで、そのルートに名前がつけられる

    ```php
    Route::get('user/profile', function () {
        //
    })->name('profile');
    ```

- その名前をグローバルなroute関数で使用することで、URLを生成したり、リダイレクトしたりできる

    ```php
    // URLの生成
    $url = route('profile');
    
    // パラメータを渡しながら URL を生成
    $url = route('profile', ['id' => 1]);
    
    // リダイレクトの生成
    return redirect()->route('profile');
    ```

## ルートグループ
- 多くのルートで共通なミドルウェアや名前空間のようなルート属性をルートごとに定義するのではなく、一括して適用するための手法
- グループ中の全ルートにミドルウェアを指定するには、そのグループを定義する前にmiddlewareメソッドを使用する

    ```php
    Route::middleware(['first', 'second'])->group(function () {
        Route::get('/', function () {
            // firstとsecondミドルウェアを使用
        });
    
        Route::get('user/profile', function () {
            // firstとsecondミドルウェアを使用
        });
    });
    ```

- 名前空間
    - グループ内のコントローラに同じPHP名前空間を指定する場合は、namespaceメソッドを使用する
        - ```App\Http\Controllers``` 名前空間をコントローラルート登録時に毎回指定しなくても済むように、デフォルトでRouteServiceProviderが名前空間グループの中でroutes.phpファイルを読み込み、指定している

    ```php
    Route::namespace('Admin')->group(function () {
        // "App\Http\Controllers\Admin"名前空間下のコントローラ
    });
    ```

- サブドメインルーティング
    - ルートURIにおけるルートパラメーターのように指定できる
    - サブドメインはグループを定義する前に、domainメソッドを呼び出し指定する

    ```php
    Route::domain('{account}.myapp.com')->group(function () {
        Route::get('user/{id}', function ($account, $id) {
            //
        });
    });
    ```

- ルートプレフィックス
    - グループ内の各ルートに対して、指定されたURIのプレフィックスを指定するために prefix メソッドを使用する

    ```php
    Route::prefix('admin')->group(function () {
        Route::get('users', function () {
            // Matches The "/admin/users" URL
        });
    });
    ```

- ルート名プリフィックス
    - 「ルートプレフィックス」とは異なるので注意！
    - 指定した文字列をグループ内の全てのルートの前に追加する
    - 「.（ドット）」で終わるようにすべし

    ```php
    Route::name('admin.')->group(function () {
        Route::get('users', function () {
            // "admin.users"という名前へ結合したルート…
        })->name('users');
    });
    ```

## モデル結合ルート
- ルートパラメータを使用してモデルを取得する場合、わざわざ取得処理を書かなくても自動で対応するモデルを引数として渡してくれるよ

- 暗黙の結合
    - タイプヒントされた変数名とルートセグメント名が一致する場合、ルートかコントローラアクション中にEloquentモデルが定義されていると、自動的に依存解決

    ```php
    Route::get('api/users/{user}', function (App\User $user) {
        return $user->email;
    });
    ```
    - 一致するモデルインスタンスがデータベースへ存在しない場合、404 HTTPレスポンスが自動的に生成される

- キー名のカスタマイズ
    - 指定されたモデルクラス取得時に、id以外のデータベースカラムをモデル結合で使用したい場合、EloquentモデルのgetRouteKeyNameメソッドをオーバーライドすれば実現できる

    ```php
    /**
     * モデルのルートキーの取得
     *
     * @return string
     */
    public function getRouteKeyName()
    {
        return 'slug';
    }
    ```

- 明示的な結合
    - RouteServiceProviderクラスのbootメソッドの中で明示的なモデル結合を定義する

    ```php
    public function boot()
    {
        parent::boot();
    
        Route::model('user', App\User::class);
    }
    ```

    ```php
    Route::get('profile/{user}', function (App\User $user) {
        //
    });
    ```

    - 上記により、{user}パラメーターをApp\Userモデルへ結合しているため、Userインスタンスはルートへ注入される

- 依存解決ロジックのカスタマイズ
    - 独自の依存解決ロジックを使いたい場合は、Route::bindメソッドを使う

    ```php
    /**
     * アプリケーションサービスの初期処理
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    
        Route::bind('user', function ($value) {
            return App\User::where('name', $value)->first() ?? abort(404);
        });
    }
    ```

    - 別の方法として、EloquentモデルのresolveRouteBindingメソッドをオーバーライドすることでも実現できる

    ```php
    /**
     * 結合値のモデル取得
     *
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value)
    {
        return $this->where('name', $value)->first() ?? abort(404);
    }
    ```

## フォールバックルート
- 受け取ったリクエストが他のルートに一致しない場合に実行するルートを定義できる
- アプリケーションのルート登録で常に一番最後に行うこと

    ```php
    Route::fallback(function () {
        //
    });
    ```

## レート制限
- アプリケーションのルートに対してレート制限をかけるミドルウェア

    ```php
    Route::middleware('auth:api', 'throttle:60,1')->group(function () {
        Route::get('/user', function () {
            //
        });
    });
    ```

    - 上記は、「認証済みのユーザーが１分間に６０回のアクセスを許すルートグループ」を定義している

- 動的レート制限
    - 認証済みUserモデルの属性に基づいて、最大リクエストを動的に指定することができる

    ```php
    Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
        Route::get('/user', function () {
            //
        });
    });
    ```

    - 上記は、User.rate_limit = 30 とすると、「1分間に30回のアクセスを許可」となる

## 擬似フォームメソッド
- PUT、PATCH、DELETEルートを定義する時、フォームに_method隠しフィールドを追加する必要がある

    ```html
    <form action="/foo/bar" method="POST">
        <input type="hidden" name="_method" value="PUT">
        <input type="hidden" name="_token" value="{{ csrf_token() }}">
    </form>
    ```

# ミドルウェア
- アプリケーションへ送信されたHTTPリクエストをフィルタリングする、便利なメカニズム
- ミドルウェアは全部、app/Http/Middlewareディレクトリに設置される

## ミドルウェア定義
- 以下コマンドでミドルウェアを作成できる

    ```bash
    $ php artisan make:middleware CheckAge
    ```

- ミドルウェアとは、HTTPリクエストがアプリケーションに届くまでに通過する、数々の「レイヤー（層）」と捉えることができる
- ちなみに、全てのミドルウェアはサービスコンテナにより依存解決される

## Before／Afterミドルウェア
- handle メソッド内の $next クロージャの呼び出し位置により、実行タイミングをリクエストの前後に指定できる
    - リクエスト前

    ```php
    public function handle($request, Closure $next)
    {
        // アクションを実行…

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

    - リクエスト後

    ```php
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // アクションを実行…

        return $response;
    }
    ```

## ミドルウェア登録
- グローバルミドルウェア
    - 全HTTPリクエストで実行したい場合は、app/Http/Kernel.php クラスの $middleware プロパティへ追加

- ミドルウェアをルートへ登録
    - 特定のルートのみに対しミドルウェアを指定したい場合は、先ず ```app/Http/Kernel.php``` ファイルの ```$routeMiddleware``` プロパティにミドルウェアの短縮キーを登録する
    - 次に、ルートに対しミドルウェアを指定する

    ```php
    Route::get('/', function () {
        //
    })->middleware('first', 'second');
    ```

    - middleware メソッドの引数にクラスを渡すこともできる

- ミドルウェアグループ
    - 多くのミドルウェアを一つのキーによりまとめ、ルートへ簡単に指定できる
    - ```app/Http/Kernel.php``` ファイルの ```$middlewareGroups``` プロパティ

- ミドルウェアの優先付け
    - 特定の順番でミドルウェアを実行する必要がある場合
    - ```app/Http/Kernel.php``` ファイルの ```$middlewarePriority``` プロパティ
        - グローバルではないミドルウェアを常に指定順に強要する

## ミドルウェアパラメータ
- 追加のカスタムパラメーターを受け取ることができる
- 追加のミドルウェアパラメーターは、ミドルウェアの$next引数の後に渡される

    ```php
    /**
     * リクエストフィルターを実行
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $role
     * @return mixed
     */
    public function handle($request, Closure $next, $role)
    {
        if (! $request->user()->hasRole($role)) {
            // リダイレクト処理…
        }

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

- ミドルウェアパラメーターはルート定義時に指定され、ミドルウェア名とパラメーターを:で区切ります。複数のパラメーターはカンマで区切ります。

    ```php
    Route::put('post/{id}', function ($id) {
        //
    })->middleware('role:editor');
    ```

## 終了処理ミドルウェア
- レスポンスがブラウザへ送られる準備ができた後に処理される
    - ミドルウェアに terminate メソッドを定義し、```app/Http/Kernel.php``` ファイルでルートのリスト、もしくはグローバルミドルウェアのリストへ追加すればよい

- なお、handle と terminate それぞれで依存解決が行われる
    - 同一のミドルウェアインスタンスを使用したい場合は、コンテナのsingletonメソッドを使用し、ミドルウェアを登録する必要がある


# CSRF 保護
- webミドルウェアグループに含まれている、VerifyCsrfToken ミドルウェアが、リクエスト中のトークンとセッションに保存されているトークンが一致するか、確認する
- Blade では ```@csrf``` ディレクティブが使用できる

    ```html
    <form method="POST" action="/profile">
        @csrf
        ...
    </form>
    ```

## URIの除外
- ```VerifyCsrfToken``` ミドルウェアの ```$except``` プロパティへ URI を追加することによって、CSRF 保護を除外するルートを定義できる

## X-CSRF-TOKEN
- SPA とか Javascript でページを作成する場合、サーバーサイドへの POST 内容に X-CSRF-TOKEN リクエストヘッダを追加すべき
    - HTML のメタタグに CSRF トークンを追加して、POST するときにその値を取得する、とか
- デフォルトでAxios HTTPライブラリにより、```csrf-token``` メタタグの値が、```resources/js/bootstrap.js``` へ保持される

## X-XSRF-TOKEN
- CSRF トークンを Cookie に設定できるよ

# コントローラ
- ```app/Http/Controllers``` ディレクトリに配置する
- ```$ php artisan make:controller Paths/TempController``` でパスを考慮して作成できるよ

## 基本のコントローラ
- コントローラの定義
    - コントローラ作成後、以下でルーティングできる

    ```php
    Route::get('user/{id}', 'UserController@show');
    ```

- コントローラと名前空間
    - ```App\Http\Controllers``` は RouteServiceProvider が自動で読み込んでいるから、それ以外のパスに Controller を配置した場合は、相対パスを記述すればよい
    - ```Route::get('foo', 'Photos\AdminController@method');```

- シングルアクションコントローラ
    - アクションが一つのコントローラの場合は、```__invoke()``` メソッドを定義し、コントローラ名のみのルーティング記述でよい

    ```php
    Route::get('user/{id}', 'ShowProfile');
    ```

    - ```--invokable``` オプションを指定すれば、```__invoke()``` メソッドが定義されたコントローラを作成する
        - ```$ php artisan make:controller TempController --invokable```

## コントローラミドルウェア
- ルーティング時にミドルウェアを指定する以外に、コントローラのコンストラクタで追加することもできる

```php
class UserController extends Controller
{
    /**
     * 新しいUserControllerインスタンスの生成
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}
```

- コントローラ内では、引数にクロージャを渡すことで独自のミドルウェアを定義できる

```php
$this->middleware(function ($request, $next) {
    // ...

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

- コントローラアクションの一部へミドルウェアを適用することはできますが、しかしながら、これはコントローラが大きくなりすぎたことを示しています
    - コントローラを複数の小さなコントローラへ分割することを考えてください

## リソースコントローラ
- 以下コマンドで CRUD メソッドを自動付与したコントローラを作成する

    ```bash
    $ php artisan make:controller PhotoController --resource
    ```

- 上記で作成したコントローラに対して、以下記述で全てのメソッドをルーティングできる

    ```php
    Route::resource('photos', 'PhotoController');
    ```

    - ルーティングされる内容は以下の通り

    |動詞|URI|アクション|ルート名|
    |---|---|----------|-------|
    |GET|/photos|index|photos.index|
    |GET|/photos/create|create|photos.create|
    |POST|/photos|store|photos.store|
    |GET|/photos/{photo}|show|photos.show|
    |GET|/photos/{photo}/edit|edit|photos.edit|
    |PUT/PATCH|/photos/{photo}|update|photos.update|
    |DELETE|/photos/{photo}|destroy|photos.destroy|