※前回の記事は以下です
概要
- 今回は 基礎 編
基本的なルーティング
-
Webページのルーティングは
routes/web.php
に以下のように記載しましょう- 引数は URI とクロージャ
Route::get('foo', function () { return 'Hello World'; });
- 以下のように URI とコントローラ名称、メソッド名称でも定義できるよ
Route::get('/user', 'UserController@index');
-
API のルーティングは
routes/api.php
に記載しましょう- URI に
/api
プレフィックスが自動で付くよ- ex)
http://localhost/api/user
-
App\Providers\RouteServiceProvider
でプレフィックス付けたりしてるよ
- ex)
- URI に
-
ルーティング定義に使う
Route::
ファサードは HTTP メソッド分用意されているよ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 () {});
- 複数の HTTP メソッドに登録する場合は
リダイレクト
- 一時的なリダイレクト
Route::redirect('/here', '/there');
- 恒久的なリダイレクト
Route:: permanentRedirect('/here', '/there');
ビュールート
- ある URI に対して、コントローラではなく単純にビューを返すだけの場合、以下が使える
-
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
- 第三引数はビューに渡すパラメータ。省略可能
- URI
/welcome
にアクセスがきたら、ビューファイル welcome を表示しますよ
-
ルートパラメーター
必須パラメータ
-
URI の中にパラメータを定義できる
Route::get('user/{id}', function ($id) { return 'User '.$id; });
-
複数も可能
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) { // });
ハイフンは使えないよ
任意パラメータ
-
URI の中に任意パラメータを定義できる
- デフォルト値は必ず指定すること
Route::get('user/{name?}', function ($name = 'John') { return $name; });
正規表現制約
-
ルートパラメータに正規表現で値の規則を付与できるよ
- これに違反したらどうなるの?
- 404 NotFound になった!
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()
の前に定義すること
- RouteServiceProvider@boot の
public function boot() { Route::pattern('id', '[0-9]+'); parent::boot(); }
- 上記の場合、「$id というルートパラメータを使用する全てのルートにて、数値のみ許可する」となる
- 指定した正規表現でいつもルートパラメータを制約できる
-
スラッシュのエンコード
-
where()
で正規表現を上書きしてやれば、ルートパラメータの値としてスラッシュを使用できる
Route::get('search/{search}', function ($search) { return $search; })->where('search', '.*');
-
名前付きルート
-
ルート定義にnameメソッドをチェーンすることで、そのルートに名前がつけられる
Route::get('user/profile', function () { // })->name('profile');
-
その名前をグローバルなroute関数で使用することで、URLを生成したり、リダイレクトしたりできる
// URLの生成 $url = route('profile'); // パラメータを渡しながら URL を生成 $url = route('profile', ['id' => 1]); // リダイレクトの生成 return redirect()->route('profile');
ルートグループ
- 多くのルートで共通なミドルウェアや名前空間のようなルート属性をルートごとに定義するのではなく、一括して適用するための手法
-
グループ中の全ルートにミドルウェアを指定するには、そのグループを定義する前にmiddlewareメソッドを使用する
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ファイルを読み込み、指定している
-
Route::namespace('Admin')->group(function () { // "App\Http\Controllers\Admin"名前空間下のコントローラ });
- グループ内のコントローラに同じPHP名前空間を指定する場合は、namespaceメソッドを使用する
-
サブドメインルーティング
- ルートURIにおけるルートパラメーターのように指定できる
- サブドメインはグループを定義する前に、domainメソッドを呼び出し指定する
Route::domain('{account}.myapp.com')->group(function () { Route::get('user/{id}', function ($account, $id) { // }); });
-
ルートプレフィックス
- グループ内の各ルートに対して、指定されたURIのプレフィックスを指定するために prefix メソッドを使用する
Route::prefix('admin')->group(function () { Route::get('users', function () { // Matches The "/admin/users" URL }); });
-
ルート名プリフィックス
- 「ルートプレフィックス」とは異なるので注意!
- 指定した文字列をグループ内の全てのルートの前に追加する
- 「.(ドット)」で終わるようにすべし
Route::name('admin.')->group(function () { Route::get('users', function () { // "admin.users"という名前へ結合したルート… })->name('users'); });
モデル結合ルート
ルートパラメータを使用してモデルを取得する場合、わざわざ取得処理を書かなくても自動で対応するモデルを引数として渡してくれるよ
-
暗黙の結合
- タイプヒントされた変数名とルートセグメント名が一致する場合、ルートかコントローラアクション中にEloquentモデルが定義されていると、自動的に依存解決
Route::get('api/users/{user}', function (App\User $user) { return $user->email; });
- 一致するモデルインスタンスがデータベースへ存在しない場合、404 HTTPレスポンスが自動的に生成される
-
キー名のカスタマイズ
- 指定されたモデルクラス取得時に、id以外のデータベースカラムをモデル結合で使用したい場合、EloquentモデルのgetRouteKeyNameメソッドをオーバーライドすれば実現できる
/** * モデルのルートキーの取得 * * @return string */ public function getRouteKeyName() { return 'slug'; }
-
明示的な結合
- RouteServiceProviderクラスのbootメソッドの中で明示的なモデル結合を定義する
public function boot() { parent::boot(); Route::model('user', App\User::class); }
Route::get('profile/{user}', function (App\User $user) { // });
- 上記により、{user}パラメーターをApp\Userモデルへ結合しているため、Userインスタンスはルートへ注入される
-
依存解決ロジックのカスタマイズ
- 独自の依存解決ロジックを使いたい場合は、Route::bindメソッドを使う
/** * アプリケーションサービスの初期処理 * * @return void */ public function boot() { parent::boot(); Route::bind('user', function ($value) { return App\User::where('name', $value)->first() ?? abort(404); }); }
- 別の方法として、EloquentモデルのresolveRouteBindingメソッドをオーバーライドすることでも実現できる
/** * 結合値のモデル取得 * * @param mixed $value * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveRouteBinding($value) { return $this->where('name', $value)->first() ?? abort(404); }
フォールバックルート
- 受け取ったリクエストが他のルートに一致しない場合に実行するルートを定義できる
-
アプリケーションのルート登録で常に一番最後に行うこと
Route::fallback(function () { // });
レート制限
-
アプリケーションのルートに対してレート制限をかけるミドルウェア
Route::middleware('auth:api', 'throttle:60,1')->group(function () { Route::get('/user', function () { // }); });
- 上記は、「認証済みのユーザーが1分間に60回のアクセスを許すルートグループ」を定義している
-
動的レート制限
- 認証済みUserモデルの属性に基づいて、最大リクエストを動的に指定することができる
Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () { Route::get('/user', function () { // }); });
- 上記は、User.rate_limit = 30 とすると、「1分間に30回のアクセスを許可」となる
擬似フォームメソッド
-
PUT、PATCH、DELETEルートを定義する時、フォームに_method隠しフィールドを追加する必要がある
<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ディレクトリに設置される
ミドルウェア定義
-
以下コマンドでミドルウェアを作成できる
$ php artisan make:middleware CheckAge
ミドルウェアとは、HTTPリクエストがアプリケーションに届くまでに通過する、数々の「レイヤー(層)」と捉えることができる
ちなみに、全てのミドルウェアはサービスコンテナにより依存解決される
Before/Afterミドルウェア
-
handle メソッド内の $next クロージャの呼び出し位置により、実行タイミングをリクエストの前後に指定できる
- リクエスト前
public function handle($request, Closure $next) { // アクションを実行… return $next($request); }
- リクエスト後
public function handle($request, Closure $next) { $response = $next($request); // アクションを実行… return $response; }
ミドルウェア登録
-
グローバルミドルウェア
- 全HTTPリクエストで実行したい場合は、app/Http/Kernel.php クラスの $middleware プロパティへ追加
-
ミドルウェアをルートへ登録
- 特定のルートのみに対しミドルウェアを指定したい場合は、先ず
app/Http/Kernel.php
ファイルの$routeMiddleware
プロパティにミドルウェアの短縮キーを登録する - 次に、ルートに対しミドルウェアを指定する
Route::get('/', function () { // })->middleware('first', 'second');
- middleware メソッドの引数にクラスを渡すこともできる
- 特定のルートのみに対しミドルウェアを指定したい場合は、先ず
-
ミドルウェアグループ
- 多くのミドルウェアを一つのキーによりまとめ、ルートへ簡単に指定できる
-
app/Http/Kernel.php
ファイルの$middlewareGroups
プロパティ
-
ミドルウェアの優先付け
- 特定の順番でミドルウェアを実行する必要がある場合
-
app/Http/Kernel.php
ファイルの$middlewarePriority
プロパティ- グローバルではないミドルウェアを常に指定順に強要する
ミドルウェアパラメータ
- 追加のカスタムパラメーターを受け取ることができる
-
追加のミドルウェアパラメーターは、ミドルウェアの$next引数の後に渡される
/** * リクエストフィルターを実行 * * @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); }
-
ミドルウェアパラメーターはルート定義時に指定され、ミドルウェア名とパラメーターを:で区切ります。複数のパラメーターはカンマで区切ります。
Route::put('post/{id}', function ($id) { // })->middleware('role:editor');
終了処理ミドルウェア
-
レスポンスがブラウザへ送られる準備ができた後に処理される
- ミドルウェアに terminate メソッドを定義し、
app/Http/Kernel.php
ファイルでルートのリスト、もしくはグローバルミドルウェアのリストへ追加すればよい
- ミドルウェアに terminate メソッドを定義し、
-
なお、handle と terminate それぞれで依存解決が行われる
- 同一のミドルウェアインスタンスを使用したい場合は、コンテナのsingletonメソッドを使用し、ミドルウェアを登録する必要がある
CSRF 保護
- webミドルウェアグループに含まれている、VerifyCsrfToken ミドルウェアが、リクエスト中のトークンとセッションに保存されているトークンが一致するか、確認する
-
Blade では
@csrf
ディレクティブが使用できる<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
でパスを考慮して作成できるよ
基本のコントローラ
-
コントローラの定義
- コントローラ作成後、以下でルーティングできる
Route::get('user/{id}', 'UserController@show');
-
コントローラと名前空間
-
App\Http\Controllers
は RouteServiceProvider が自動で読み込んでいるから、それ以外のパスに Controller を配置した場合は、相対パスを記述すればよい Route::get('foo', 'Photos\AdminController@method');
-
-
シングルアクションコントローラ
- アクションが一つのコントローラの場合は、
__invoke()
メソッドを定義し、コントローラ名のみのルーティング記述でよい
Route::get('user/{id}', 'ShowProfile');
-
--invokable
オプションを指定すれば、__invoke()
メソッドが定義されたコントローラを作成する$ php artisan make:controller TempController --invokable
- アクションが一つのコントローラの場合は、
コントローラミドルウェア
- ルーティング時にミドルウェアを指定する以外に、コントローラのコンストラクタで追加することもできる
class UserController extends Controller
{
/**
* 新しいUserControllerインスタンスの生成
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
}
}
- コントローラ内では、引数にクロージャを渡すことで独自のミドルウェアを定義できる
$this->middleware(function ($request, $next) {
// ...
return $next($request);
});
- コントローラアクションの一部へミドルウェアを適用することはできますが、しかしながら、これはコントローラが大きくなりすぎたことを示しています
- コントローラを複数の小さなコントローラへ分割することを考えてください
リソースコントローラ
-
以下コマンドで CRUD メソッドを自動付与したコントローラを作成する
$ php artisan make:controller PhotoController --resource
-
上記で作成したコントローラに対して、以下記述で全てのメソッドをルーティングできる
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