Laravel 9.x ルーティングの解説

## ルーティングとは

LaravelのルーティングはHTTPリクエストのパスや内容に対応するコントローラに処理を渡す役目をします。

## 公式ドキュメント

https://readouble.com/laravel/9.x/ja/routing.html

## ディレクトリ構成

ルーティングファイルは `routes` ディレクトリの中にいくつかあります。

|ファイル|説明|
|---|---|
|api.php|APIのルーティング|
|channels.php|ブロードキャストチャンネルのルーティング|
|console.php|コンソールのルーティング|
|web.php|一般のWebページのルーティング|

主に利用するのは `web.php` または `api.php` になるかと思います。
LaravelをJSONで返すAPIとして利用する場合は `api.php` に書いていくことになります。

## ビューを表示する

### 基本

```php:routes/web.php
Route::get('/', function () {
    return view('welcome');
});
```

第一引数にルーティングパス、第二引数にコールバックまたはコントローラクラスを渡してルーティングに対応する処理を登録します。
コールバックの中にロジックを書くとルーティングファイルが肥大化するのでコントローラを登録しましょう。

ロジックがなく、シンプルに画面を表示したりリダイレクトのみ行いたい場合はコントローラを登録しなくても良いと思います。
また、view関数で `view('welcome')` と指定した場合、 `resources/views/welcome.blade.php` を指定したことになります。

### 補足: PHP7.4以降のコールバックの書き方

PHP7.4からはアロー関数が利用できるのでコールバックをよりシンプルに記述できます。

```php:routes/web.php
Route::get('/', fn () => view('welcome'));
```

https://www.php.net/manual/ja/functions.arrow.php

### ビューを表示するシンプルな書き方

ビューを表示するだけなら `view` 関数を使うと良いでしょう。

```php:routes/web.php
Route::view('/', 'welcome');
```

### 文字を返すだけでも表示してくれる

とりあえず文字だけ表示したいって時もシンプルに書けます。

```php:routes/web.php
Route::get('health-check', fn () => 'ok');
```

## ルーティングの優先順位について

Laravelのルーティングは上から定義した順に優先されます。

:::note alert
```php:routes/web.php
Route::get('articles/{id}', [ArticleController::class, 'show']);
Route::get('articles/create', [ArticleController::class, 'create']);
```
- '/articles/create' にアクセスした際、ルートパラメータ(`{id}`)に `create` が渡ってしまい、 `show` メソッドが呼ばれてしまいます。
:::

:::note
```php:routes/web.php
Route::get('articles/create', [ArticleController::class, 'create']);
Route::get('articles/{id}', [ArticleController::class, 'show']);
```
- ルートパラメータ付きの定義は下に書くようにします。
:::

## リダイレクトする

単純なリダイレクト処理を行う場合は `redirect` 関数を使うと良いでしょう。
`302` ステータスコードを返します。

```php:routes/web.php
Route::redirect('here', 'there');
```

この例だと `/here` にアクセスすると `/there` にリダイレクトします。

## HTTPリクエストメソッド

HTTPリクエストメソッドに対応するルーティングを登録できます。

```php:routes/web.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);
```

https://developer.mozilla.org/ja/docs/Web/HTTP/Methods

<!--

## ルートパラメータ

```php:routes/web.php
Route::get('articles/{id}', fn () => 'Article: ' . $id);
Route::get('articles/{id}/comments/{comment}', fn ($articleId, $commentId) => 'Article: ' . $articleId . ' Comment: ' . $commentId);
```

- ルートパラメータはIDなど必須な値を指定します。

-->

## ルーティングとコントローラの命名

ルートは複数形(articles)
コントローラは単数形または複数形(ArticleController, ArticlesController)
名前付きルートはドット表記(articles.index)

で命名されることが多いです。

## リソースコントローラ

```php:routes/web.php
Route::resource('articles', ArticleController::class);
```

`resource` 関数で登録すると下記のルートを登録したのと同じ内容になります。

```php:routes/web.php
Route::get('articles', [ArticleController::class, 'index'])->name('articles.index');
Route::get('articles/create', [ArticleController::class, 'create'])->name('articles.create');
Route::post('articles', [ArticleController::class, 'store'])->name('articles.store');
Route::get('articles/{article}', [ArticleController::class, 'show'])->name('articles.show');
Route::get('articles/{article}/edit', [ArticleController::class, 'edit'])->name('articles.edit');
Route::put('articles/{article}', [ArticleController::class, 'update'])->name('articles.update');
Route::delete('articles/{article}', [ArticleController::class, 'destroy'])->name('articles.destroy');
```

業務ではシンプルなCRUDではないことも多いのでリソースコントローラは使用せず、個別にルートを定義しています。

### 補足: リソースコントローラのテンプレートを作成

```shell-session
$ php artisan make:controller ArticleController --resource
```

`--resource` or `-r` オプションを付けてコマンドを実行するとリソースコントローラのテンプレートを生成できます。

<details><summary>app/Http/Controllers/ArticleController.php クリックして表示</summary><div>

```php:app/Http/Controllers/ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}
```

</div></details>

### APIリソースコントローラ

```php:routes/web.php
Route::apiResource('articles', ArticleController::class);
```

`create`, `edit` を除いたAPI用のルートを登録します。

```php:routes/web.php
Route::get('articles', [ArticleController::class, 'index'])->name('articles.index');
Route::post('articles', [ArticleController::class, 'store'])->name('articles.store');
Route::get('articles/{article}', [ArticleController::class, 'show'])->name('articles.show');
Route::put('articles/{article}', [ArticleController::class, 'update'])->name('articles.update');
Route::delete('articles/{article}', [ArticleController::class, 'destroy'])->name('articles.destroy');
```

### 補足: APIリソースコントローラのテンプレートを作成

```shell-session
$ php artisan make:controller ArticleController --api
```

<details><summary>app/Http/Controllers/ArticleController.php クリックして表示</summary><div>

```php:app/Http/Controllers/ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}
```

</div></details>

## 補足: シングルアクションコントローラ

シングルアクションコントローラとは、単一のアクションのみ実行する責務を持つコントローラです。
publicメソッドを1つだけ持たせます。シングルアクションにすることで様々なメリットがあります。

- コードの肥大化が防げる
  - 1ファイルの行数が減るため
- 可読性が向上する
  - 使用しないプロパティやメソッドを読む必要がないため
- メンテナンス性が向上する
  - クラスを分けることでコンフリクトが減る
  - 他のアクションの影響を受けないので削除しやすい

```php
Route::get('articles', ArticleIndexControlelr::class)->name('articles.index');
```

```shell-session
$ php artisan make:controller ArticleIndexControlelr --invokable
```

`--invokable` or `-i` オプションを付けてコマンドを実行するとシングルアクションコントローラのテンプレートを生成できます。

```php:app/Http/Controllers/ArticleIndexControlelr.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ArticleIndexControlelr extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function __invoke(Request $request)
    {
        //
    }
}
```

ルーティングでクラス名のみ渡した時は `__invoke` メソッドが実行されます。
https://www.php.net/manual/ja/language.oop5.magic.php#object.invoke

シングルアクションコントローラの場合は `__invoke` 以外にpublicのメソッドを持ってはいけません。(言語として強制される訳ではありませんが)
`__construct` のようなコンストラクタは除いて、シングルアクションコントローラ内でメソッドを分けたい場合はprivateメソッドを定義しましょう。

https://e-seventh.com/laravel-single-action-controller/

## 補足: ルーティングパスの命名規則

- 前後にスラッシュを付けない
  - OK: `Route::get('users', ...)`
  - NG: `Route::get('/users', ...)`
  - NG: `Route::get('users/', ...)`
- 名詞を使う、動詞は含めない
  - OK: `Route::get('users', ...)`
  - OK: `Route::delete('users/{id}', ...)`
  - NG: `Route::get('getUsers', ...)`
  - NG: `Route::post('users/{id}/delete', ...)`
- 区切り文字はハイフンを使う
  - OK: `Route::get('users/{id}/first-name', ...)`
  - NG: `Route::get('users/{id}/firstName', ...)`
  - NG: `Route::get('users/{id}/first_name', ...)`
- 小文字、特殊記号は避ける
- ファイル拡張子を避ける
  - OK: `Route::get('users', ...)`
  - OK: `Route::get('users.csv', ...)`
- コマンドではなく、リソースベースで命名する
  - OK: `Route::post('users/{id}/activate', ...)`
  - NG: `Route::post('activate-users/{id}', ...)`
- Route::resourceを避ける
  - 使用するルートを1つ1つ定義する
  - ※これは個人の意見
- アンチパターンを避ける
  - [悪いWeb API設計](https://wiki.suikawiki.org/n/%E6%82%AA%E3%81%84Web%20API%E8%A8%AD%E8%A8%88#gsc.tab=0)

## 補足: ルーティング系コマンド

### ルーティングの一覧を表示するコマンド

```shell-session
$ php artisan route:list
```

### ルーティングをキャッシュするコマンド

```shell-session
$ php artisan route:cache
```

`bootstrap/cache/routes-v7.php` にキャッシュファイルが生成されます。
ちなみにLaravel 6.xまでは `bootstrap/cache/routes.php` に生成されていました。

https://www.larajapan.com/2021/03/21/laravel-7-x%E6%9B%B4%E6%96%B0%E3%81%A7%E7%99%BA%E8%A6%8B%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8%EF%BC%883%EF%BC%89%E3%83%AB%E3%83%BC%E3%83%88%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5/

### ルーティングのキャッシュをクリアするコマンド

```shell-session
$ php artisan route:clear
```

<!--

## ルートグループ

力尽きたのであとで追記します。

## ルートミドルウェア

力尽きたのであとで追記します。

## ルートパラメータ制約

力尽きたのであとで追記します。

## ルートモデルバインディング

力尽きたのであとで追記します。

## ルーティングのサービスプロバイダー

力尽きたのであとで追記します。

-->