Laravel Jetstreamのビュー・ルート・機能のカスタマイズ方法

# Laravel Jetstreamの概要

Laravel JetstreamはLaravel 8から標準になった認証機能のパッケージで、7までのlaravel/uiや、5.8までの組み込みの`artisan make:auth`の代わりとなるパッケージ。

Laravel本体とは独立して存在しているが、Laravelのインストール時にいっしょに設定することも可能。

```
: composerとartisanで
composer require laravel/jetstream
php artisan jetstream:install livewire
```

```
: あるいは、Laravelのインストール時に
laravel new <DIR> --jet
```

Jetstreamは単体のパッケージではなく、いくつかのパッケージを寄せ集めて、その上で統合している。各パッケージが担当する機能については以下のようになっている。

* Laravel Fortifyがセッションベースの認証を。
* Laravel SanctumがAPI認証を。
* LivewireかInertia.jsがユーザプロフィール・チーム管理周りのUIのビューを。
* Tailwind CSSがUIのデザインを。

さらにJetstream自体がルートやビュー、コントローラのスカフォールド等を担当する。

この記事ではビューにはLivewireを使う前提とし、Inertia.jsは取り扱わない。またTailwind CSSも取り扱わない。

その前提で、それぞれの機能をどう変更するべきかの簡易なリファレンスを提供する。


参考:
[Jetstreamのインストール/インストール 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/installation.html#dummy)


# ビューを変更するには

機能自体は提供されたものをそのまま使うとしても、デザインをそのまま使うことは少ない。また機能を変更する場合にはデザインも同時に変更することがほとんどだろう。たとえばログインにメールアドレスとは別にログインIDを用意して使うようにするだけでも、ログインフォームの項目名の修正が必要だ。

ということでまずビューの変更方法から始める。

Laravel Jetstreamで提供されるビューは大まかに以下の3つに分けられる。

1. `auth`以下にある、Fortifyによる基本的な認証機能のためのもの。
2. `vendor/laravel/jetstream/resources/views/components`にある一般的なUIのためのBladeコンポーネント。
3. `api`, `profile`, `teams`以下にあるLivewireコンポーネントのビュー。


## 認証機能のための`auth`以下のビュー

`resources/views/auth`以下に存在しているビューは、単純に直接編集すればよい。ログインフォームであれば、`resources/views/auth/login.blade.php`だ。

中身を見てみると、見慣れないものがある。`<x-guest-layout>`のような、`x-`接頭辞のあるタグだ。

これはLaravel 7(8ではなく)から導入されたコンポーネントの新しい書き方で、laravel/uiでは使われていなかった。

`<x-guest-layout>`は`app/View/Components/GuestLayout.php`コンポーネント経由で`resources/views/layouts/guest.blade.php`を読み込んでいるので、このファイルを変更すればよい。

これらのビューはFortifyのためのものではあるが、Jetstreamによって提供されている。


参考:
[コンポーネントの表示/Bladeテンプレート 8.x Laravel](https://readouble.com/laravel/8.x/ja/blade.html#displaying-components)


## 一般的なUIのための`x-jet-`接頭辞のBladeコンポーネント

`auth`以下のビューを見てみると、接頭辞が`x-jet-`となるコンポーネントが見つかる。これらはJetstreamが登録するBladeコンポーネントで、実体は`vendor/laravel/jetstream/resources/views/components/`以下にある。

ボタンやモーダルなど、ビューの再利用性を上げるためによく使うコンポーネントが登録されているが、これらも変更なしで使うことはほとんどないだろう。

変更する場合は`artisan vendor:publish --tag=jetstream-views`で`resources/views`以下に出力した上で編集する。

なおこれらのコンポーネントは`Laravel\Jetstream\JetstreamServiceProvider`( `vendor/laravel/jetstream/src/JetstreamServiceProvider.php`, `App\Providers\JetstreamServiceProvider`とは別物。Jetstreamのほとんどの機能はこのサービスプロバイダによってLaravelアプリケーション内で使用可能になっている。)で一つずつエイリアスを登録されている。そのため同じディレクトリにビューファイルを保存しても、`x-jet-`接頭辞で読み込むことはできない。

コンポーネントのエイリアスは、`Blade::getClassComponentAliases()`で一覧できる。エイリアスが登録されていればそれが最優先で、次にクラスベースコンポーネント、最後に`resources/views/components`以下のコンポーネント(匿名コンポーネント)という優先順で読み込まれる。


## Livewireコンポーネント

`auth`以下のビューでは使われていないが、ログイン後のレイアウトである`layouts.app`やユーザプロフィールページである`profile.show`などでは、`@livewire`というBladeディレクティブが使われている。

`@livewire`は`@include`のような挙動をする。ディレクティブのある位置にほかのビューを読み込むような挙動だ。ただしBladeのビューではなく、Livewireコンポーネントを読み込む。

Jetstreamが提供するビューを変更する中で、このディレクティブに対する理解も必要になってくる。とはいえLivewire自体を詳細に解説するのがこの記事の目的ではないため、説明は最小限に留める。ほか同様、どこを変更すればいいのかのみ説明する。

`artisan jetstream:install livewire`した時点で存在するビュー内で、`@livewire()`に渡されている文字列には、`'api'`, `'profile'`, `'teams'`が接頭辞に来るものと、`'navigation-dropdown'`がある。

これらの名前と対応するビューは`resources/views`以下に、Bladeビューと同じ対応方法でビューが存在する。`'api.api-token-manager'`であれば、`resources/views/api/api-token-manager.blade.php`にある。

これだけ見るとLivewireコンポーネントがBladeビューのような名前・ファイルの対応になっているように見えるが、じつは違う。

むしろBladeコンポーネントに近く、Livewireコンポーネントの名前は、ビューではなくクラスに対応している。Bladeコンポーネントはクラスがない場合はビューにフォールバックしているが、Livewireコンポーネントの場合はそれもない。

`foo.bar`というコンポーネントは、`App\Livewire\Foo\Bar`と関連付いている。このコンポーネントが最終的に`resources/views/foo/bar.blade.php`というビューを読み込んでいたとしても、それはBarクラス内でそのビューを読み込んでいるからという以上のことはない。誤解しやすいところなので注意しよう。

`artisan livewire:make Foo\\Bar`でLivewireコンポーネントのスカフォールドを作成してみるとわかりやすい。

`resources/views/livewire/foo/bar.blade.php`というビューが作られるが、これは`Bar`クラスの`render()`メソッドで明示的に読み込むようにしているから使われるだけで、これを変えてしまえば、このビューは無視される。

Jetstreamが提供するLivewireコンポーネントは`Laravel\Jetstream\JetstreamServiceProvider`でエイリアスが登録されている。

Bladeコンポーネントと違い、エイリアスを一覧するメソッドは用意されていないが、`artisan tinker`から`dump(app('livewire'))`などしてみると、`$componentAliases`というプロパティの中で定義が確認できる。

エイリアスが登録されていない場合は、名前からクラス名に変更され、読み込まれる。上記の`foo.bar`であれば、`App\Livewire\Foo\Bar`となる。


参考:
最近Livewireの公式ドキュメントが日本語訳された。Laravel公式ドキュメントの日本語訳者によるものだ。
[2.x Livewire](https://readouble.com/livewire/2.x/ja/)


## 使用するビュー自体を変更する

ここまでは主にビューの内容を変更する方法について書いた。だが使用するビュー自体を変更したい場合もあるだろう。そのようなカスタマイズも用意されている。簡単に紹介する。


### Fortifyのビュー

Fortifyのビューは、`Laravel\Jetstream\JetstreamServiceProvider`内で、`Fortify::viewPrefix('auth.')`というメソッドで、`auth`以下から読み込むように指示されている。

変更する場合は`App\Providers\JetstreamServiceProvider::boot()`内などで、`viewPrefix()`を実行すればよい。

```php
<?php

namespace App\Providers;

use Laravel\Fortify\Fortify;

class JetstreamServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Fortify::viewPrefix('myauth.');
    }
}
```

`Laravel\Fortify\Fortify::viewPrefix()`の実装を見てみるとわかるが、使用する個々のビューを変更することも可能だ。同クラス内にビューごとに専用のメソッドが用意されている。

```php
<?php

namespace App\Providers;

use Laravel\Fortify\Fortify;

class JetstreamServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Fortify::loginView('login');
    }
}
```

とすれば、`resources/views/login`が使用される。


参考:
[認証 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/authentication.html) の"ビューレンダリングのカスタマイズ"以下。


### Bladeコンポーネント

`Laravel\Jetstream\JetstreamServiceProvider`で、`Blade::component()`によってエイリアスが登録されている。エイリアスは上書きできるので、これも`App\Providers\JetstreamServiceProvider::boot()`内などで登録すればよい。

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;

class JetstreamServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Blade::component('my-section-border', 'jet-section-border');
    }
}
```

ビューは開発環境でもキャッシュされ、ビュー自体の変更がなければ再コンパイルされないため、Bladeコンポーネントを変更した際はビューのキャッシュをクリアしないと有効にならない場合がある。そのような場合は`artisan view:clear`する。


### Livewireコンポーネント

Bladeコンポーネント同様だが、エイリアスを登録するメソッドは`Livewire::component()`だ。

```php
<?php

namespace App\Providers;

use App\Http\Livewire\MyNavigationDropdown;
use Livewire\Livewire;

class JetstreamServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Livewire::component('navigation-dropdown', MyNavigationDropdown::class);
    }
}
```

# ルートを変更するには

Jetstreamによって定義されるルートには2系統ある。

1. Jetstreamによる、ユーザプロフィールやチーム関連、またLivewireやInertia.js用のAPI関連のルート。
2. Fortifyによるログインページなどの認証周りのルート。

`artisan route:list`でどのようなルートが登録されているか確認できる。`login`, `logout`, `register`などは2のFortifyによるもので、`profile.show`や、名前のない`api/user`パスのルートは、1のJetstreamによるものだ。

それぞれ変更方法は微妙に違う。


## Jetstreamによるルート

JetstreamによるルートはいつものJetstreamパッケージ内のJetstreamServiceProviderから定義されている。実体はLivewireを使用している場合は`vendor/laravel/jetstream/routes/livewire.php`となる。

ここでは機能の有効無効によるオン・オフ以上のカスタマイズはできない。たとえばチーム機能の有効・無効によってチーム機能関連のルートは有効・無効になるが、それだけだ。

```php
<?php

# vendor/laravel/jetstream/routes/livewire.php
# 
# Copyright (c) Taylor Otwell
# https://github.com/laravel/jetstream/blob/2.x/LICENSE.md

use Illuminate\Support\Facades\Route;
use Laravel\Jetstream\Http\Controllers\CurrentTeamController;
use Laravel\Jetstream\Http\Controllers\Livewire\TeamController;
use Laravel\Jetstream\Jetstream;

Route::group(['middleware' => config('jetstream.middleware', ['web'])], function () {
    Route::group(['middleware' => ['auth', 'verified']], function () {
        // Teams...
        # チーム機能が有効な場合のみ、以下のルートが有効になっている
        if (Jetstream::hasTeamFeatures()) {
            Route::get('/teams/create', [TeamController::class, 'create'])->name('teams.create');
            Route::get('/teams/{team}', [TeamController::class, 'show'])->name('teams.show');
            Route::put('/current-team', [CurrentTeamController::class, 'update'])->name('current-team.update');
        }
    });
});
```

なのでたとえばプロフィール編集ページのパスを変更したいと思った場合は、手動でルートを設定しなければならない。

一から設定してもいいが、Jetstreamのルートファイルをpublishして、カスタマイズすることもできる。

```
php artisan vendor:publish --tag=jetstream-routes
```

で`routes/jetstream.php`が配置されるので、それを自由にカスタマイズし、追加で、

1. RouteServiceProvider等で上記ファイルを読み込む。
2. 元のルートをJetstreamServiceProvider::register()あたりで`Jetstream::ignoreRoutes()`とすることによって無効化する。このJetstreamServiceProviderは`App\Providers`の方なので注意。

```php
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->routes(function () {
            Route::namespace($this->namespace)
                ->group(base_path('routes/jetstream.php'));
        });
    }
}
```

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Jetstream\Jetstream;

class JetstreamServiceProvider extends ServiceProvider
{
    public function register()
    {
        Jetstream::ignoreRoutes();
    }
}
```


## Fortifyによるルート

こちらはもう少しカスタマイズが考えられていて、たとえば接頭辞をつけるだけであれば`config/fortify.php`に設定を追加するだけで可能だ。`path`というキーにたとえば`'bar'`という値を設定すれば、ログインページのパスは'/bar/login'となる。

```php
<?php

# config/fortify.php

return [
    'path' => 'bar',
];
```

だがそれ以上のカスタマイズ方法は用意されていない。`vendor:publish`で出力することもできず、完全に手動でルートを設定するしかない。

元のルートを無効にする場合はJetstreamと同じように、`Fortify::ignoreRoutes()`を使う。

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class JetstreamServiceProvider extends ServiceProvider
{
    public function register()
    {
        Fortify::ignoreRoutes();
    }
}
```

その上で、`vendor/laravel/fortify/routes/routes.php`を参考に必要なルートを設定すればよい。


# 機能を変更するには

Jetstreamが提供する機能も、Fortifyが提供する機能も、コントローラアクション単位での変更はできない。ユーザ登録に`Laravel\Fortify\Http\Controllers\RegisteredUserController::store`を使わず自前のメソッドを使うような変更をしたいならば、ルートの変更の項で見たようにルート自体を差し替えるしかない。

とはいえコントローラアクションは抽象的に実装されており、変更しないでもかなりの部分までカスタマイズができるようになっている。

上記の例でいえば、じっさいのユーザ作成の実装も、作成後のレスポンスも簡単にカスタマイズ可能だ。これについては[DIによる機能の変更](#diによる機能の変更)の項で説明する。


## features設定による機能のオン・オフ

JetstreamでもFortifyでも、カスタマイズされがちな機能については簡単にオン・オフの切り替えができるようになっている。

Jetstreamではプロフィール写真のアップロード、チーム機能などが、Fortifyではユーザ登録やパスワードリセットのような、以前は`Auth::routes()`への引数で切り替えていた機能や、プロフィールの更新、2要素認証などが、簡単に切り替えられる。

それぞれ設定変数`jetstream.features`, `fortify.features`を使用する。


```php
<?php

# config/jetstream.php

use Laravel\Jetstream\Features;

return [
    'features' => [
        # プロフィールの写真のアップロードを有効にする
        Features::profilePhotos(),
    ],
];
```

ただし一部の機能は、`features`の設定だけでは不完全だ。メールアドレスの確認には、7まで同様プロバイダクラスにMustVerifyEmailインターフェイスを実装する必要があるし、チーム機能に関しては、`laravel new`や`artisan jetstream:install`の時点で有効にしておかないと、`features`で設定しただけでは動作しないどころかエラーになったりする。


参考:
[認証 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/authentication.html)の"メール確認"以下。
[プロファイル管理 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/profile-management.html)の"プロフィール写真の有効化"以下。
[API 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/api.html)の"APIサポートの有効化"以下。


## DIによる機能の変更

Fortifyが提供するコントローラでは、アクションを直接差し替えることこそできないが、重要な部分はDIによって差し替えられるようになっている。

ログインフォームの表示やログイン・ログアウト処理のための`Laravel\Fortify\Http\Controllers\AuthenticatedSessionController`を見てみると、ログインフォームを表示する`login()`メソッドは`app()`で解決したサービスを返している。

これらは単純に別のクラスをバインドすることで差し替えることもできるが、専用のメソッドも用意されている。`Fortify::loginView()`は名前通りログインフォームのビューを差し替えることができるし、`Fortify::createUsersUsing()`はユーザ作成オブジェクトを差し替えられる。前者は差し替えるクラス等を直接指定するのではなく、ビュー名を渡すこともできるのでバインドするよりも簡易だ。これらを`App\Providers\FortifyServiceProvider`などで実行する。

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Fortify::loginView('myauth.login');
        Fortify::createUsersUsing(MyCreateNewUser::class);
    }
}
```

Jetstreamが提供するコントローラでも、主にチーム関係の機能を名前がuseで始まるメソッドや、Usingで終わるメソッドで差し替えられる。


参考:
[認証 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/authentication.html)の"ビューレンダリングのカスタマイズ"以下、また"User認証のカスタマイズ"以下。
[セキュリティ 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/security.html)の"パスワード確認方法のカスタマイズ"以下。


## ログイン処理のカスタマイズ

現代のログイン処理は複雑だ。IDとパスワードによる認証だけではなく、ログイン試行回数をチェックしたり、追加の要素で認証したいかもしれない。そのようなニーズにも簡単に応えられるよう、Fortifyのログイン処理は非常にカスタマイズしやすく作られている。

具体的にはミドルウェアのような動作をする「ログインパイプライン」によって、指定した機能を連鎖的に実行する形になっている。

設定`fortify.pipelines.login`の値として、`handle()`メソッドを持ったクラスを指定すれば、それらのクラスが順番に実行される。

```php
<?php

# config/fortify.php

use Laravel\Fortify\Actions\AttemptToAuthenticate;
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;

return [
    'pipelines' => [
        'logins' => [
            EnsureLoginIsNotThrottled::class,
            RedirectIfTwoFactorAuthenticatable::class,
            AttemptToAuthenticate::class,
            PrepareAuthenticatedSession::class,
        ],
    ],
];
```

デフォルトでは2要素認証、通常のパスワード認証、認証時のセッション再生成が実行されるようになっている。このデフォルト値自体は`fortify.pipelines.login`には設定されていないので注意が必要だ。

ドキュメントにあるように、`Fortify::authenticateUsing()`を使って設定することも可能だ。


参考:
[認証 1.0 Laravel Jetstream](https://readouble.com/jetstream/1.0/ja/authentication.html)の"認証プロセスのカスタマイズ"以下。


# おまけ: `auth:sanctum`について

最後に、Jetstreamを読み解いていく中できっと気になる`auth:sanctum`について簡単に説明する。

`artisan jetstream:install`すると、`routes/web.php`に`auth:sanctum`ミドルウェア(正確には`sanctum`ガードを使うよう指示された`auth`ミドルウェア)を使うルートが登録される。

このミドルウェアはなんだろうか。Sanctumは最初に書いたようにAPI認証用の機能だが、ここで登録されているルートはダッシュボードのもので、普通にセッションベースで認証できている。

結論を言うと、`sanctum`ガードは、`sanctum.guard`設定で指定したガードか未指定であれば`web`ガードが有効な場合はそちらを見るようになっている。そのためログインしない状態で、Bearerトークンで認証するような場合以外は`'auth'`とパラメータなしで指定する場合と変わらない。つまりダッシュボードで`sanctum`ガードを使う必要はおそらくない。

`sanctum`ガードは`\Laravel\Sanctum\Guard`で実装されている。


# 最後に

Jetstreamによって導入される機能の変更方法を見てきた。ほとんどの機能は最終的には2つのサービスプロバイダ、`JetstreamServiceProvider`と`FortifyServiceProvider`(それぞれ`vendor`内の方)によって提供されている。そこだけ覚えておけば、自力で結論に辿り着くのも容易だ。


# この記事のライセンス

![クリエイティブ・コモンズ・ライセンス](https://i.creativecommons.org/l/by/4.0/88x31.png)
この文書は[CC BY(クリエイティブ・コモンズ表示4.0国際ライセンス)](http://creativecommons.org/licenses/by/4.0/)で公開する。

この文書内のサンプルコードのうち、はMITライセンスで公開する。