- Laravel8でのJetstreamを使った、ユーザー管理機能の学習の記録。「 Jetstream 導入 と ユーザー登録/更新/ログイン機能 」についての備忘録です。
- 参考:Laravel8 、 Jetstream
環境
- OS : macOS Caralina 10.15.7
- PHP : 7.4.11
- laravel : 8.11.2
- Composer : 1.10.6
今回の学習内容
No. | 概要 |
---|---|
1. |
Jetstream の導入 ・ 導入のコマンド ・ 日本語化する ・ 動作確認で、デフォルト機能を確認( 新規登録/ログイン(email+PW認証)、ユーザー情報更新) |
2. |
作成されたファイルの把握 ※ 大量にファイルが作成されるので、理解が追いついてない。。 |
3. |
例)基本の実装いろいろ ・ ログイン / アカウント登録後のリダイレクト先 の変更 ・ usersテーブルへのカラム追加 ・ user - 他モデル との紐付け ・ 所望のページにアクセス制限をつける ・ Jetstream ロゴの変更 ・ アイコン写真の登録 |
Auth とは?
- 認証(Ahthentication)や認可(Ahthorization)の意味。
- Laravelでは、デフォルトで認証機能があり、コマンドで作れる。
Jetstream とは?
- Laravel8の認証ライブラリ。
- Laravel〜5:make:auth、Laravel6〜7:laravel/uiに分離。Laravel8では、全部入り(Jetstream)になったんだとか。。
- Jetstream がやってくれることは、 ログイン、新規登録、メール検証、2段階認証、セッション管理、APIサポート(Laravel Sanctum)、チーム管理など。めっちゃやってくれる。。
1. Jetstreamの導入
【 導入のコマンド 】
Jetstreamの導入まで
// ① Jetstream のインストール
% composer require laravel/jetstream // composerを使ってインストール
% php -d memory_limit=-1 /usr/local/bin/composer require laravel/jetstream // メモリ制限エラーが出たら、メモリを上げてインストール
% laravel new project-name --jet // アプリの雛形作成のタイミングで、Jetstream をインストールも可能(Livewire/Inertiaやチーム機能を使うか?聞かれる)
// インストールが完了したら、artisan コマンドでjetstreamが使えるようになる
// ② セットアップ : JSのパッケージをインストール( livewire と inertia を選択できる)
% php artisan jetstream:install livewire
% php artisan jetstream:install livewire --teams // チーム機能を使う場合
% php artisan jetstream:install inertia // vueやreactなどを使う場合は、コッチらしい。。
// ③ マイグレーション実行
% php artisan migrate // usersテーブル、password_resetsテーブル作成のマイグレーションファイルが、自動生成されてる
// ④ JSとCSSをビルド
% npm install && npm run dev // npmコマンドが無いエラーが出たら、Nodeのインストールが必要(https://nodejs.org/ja/)
// 若しくは、別々にNode.js と NPM をインストール
% npm install
% npm run dev
// public/css/app.css、 public/js/app.js2ファイルが作成される
- ここで、ブラウザで動作確認すると、エラー発生。'Laravel\Sanctum\HasApiTokens' が見つからんらしい。。
Trait 'Laravel\Sanctum\HasApiTokens' not found
→ laravel/sanctum をインストールしてみる。
laravel/sanctumのインストール
// ⑤ laravel/sanctumのインストール
% composer require laravel/sanctum
% php -d memory_limit=-1 /usr/local/bin/composer require laravel/sanctum // メモリ制限エラーが出たら、メモリを上げてインストール
- デフォルトでは、welcomeページに、ログイン・新規登録ボタンが設置してくれてる。が、今回は、既存アプリに導入したので、ルートページは、welcomeページではない。
- welcomeページに設置された、「 ログイン・新規登録ボタン 」 部分をコピペさせてもらう。
welcomeページに設置された、ログイン・新規登録ボタン部分(welcome.blade.php)
@if (Route::has('login'))
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
@auth // ログイン中の場合
<a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 underline">ダッシュボード</a>
@else // 未ログインの場合
<a href="{{ route('login') }}" class="text-sm text-gray-700 underline">ログイン</a>
@if (Route::has('register'))
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">アカウント作成</a>
@endif
@endif
</div>
@endif
-
@auth...@endif
構文(※ Laravel7までは、@auth...@endauth)。
【 日本語化する 】
フィールドの日本語化
- バリデーションなどの翻訳データが公開されてるので、GitHubのLaravel-Lang/lang から、zipファイルをダウンロード。
- ダウンロードしたフォルダの中から、日本語化ファイルを移動↓
日本語化設定(resources/lang/jaの生成)
php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"
// resources/lang/ja に、ファイルが生成される( auth.php、 pagination.php、 passwords.php、 validation.php )
※ ただし、微妙に英語が残ってるので、気付いた箇所は、resources/lang/js.json に、力技で日本語化。
【 動作確認 】
- この段階で、新規登録とログイン機能が動く状態になってる。
- ルートページに、ログイン・新規登録ボタンが出現。
- name、email、PWは必須。
- name、emailは、Max255文字まで。
- emailは、文字列長(@を含む)と、一意性。
- PWは、8文字 / 空 / PW(確認用)と不一致 で、バリデーション。
- バリデーションの条件は、app\Actions\Fortify\CrateNewUser.php、PasswordValidationRules.php に書かれてる。
- ログイン時に、入力を何度も間違うと、一定時間ログインできなくなる。
- アカウント作成クリックで、DBにデータが保存できてる。
- ログイン / アカウント作成 をクリック → dashboardページに遷移する。
dashboardページ | ログイン状態 → ルートページ |
---|---|
localhost:8000/dashboard | localhost:8000/ |
ログイン時のボタン表示 |
- dashboardページには、Profile・ログアウトボタンが設置されてる。
Profileボタン → ユーザー情報更新ページ | ログアウトボタン |
---|---|
localhost:8000/user/profile | localhost:8000 |
ログイン中の端末もわかる。 |
ルートページへ遷移。 (未ログイン時のボタン表示) |
- ユーザー情報(name、email、photo)やPWの更新に関するバリデーション条件は、app\Actions\Fortify\UpdateUserProfileInfomation.php、UpdateUserPassword.php(※ PasswordValidationRules.phpを呼び出してる) に書かれてる。
2. 作成されたファイルたちの一部
○ ルーティング
- 認証用のルートが追加される。
- authミドルウェアによって、未ログイン時は、ログインページにリダイレクトされる仕様。
追加される認証用のルート
// web.php
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return view('dashboard');
})->name('dashboard'); // dashboard というルート名が付けられてる
// api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
○ コントローラー ( app\Http\Controllers)
- Jetstream では、不要なので作成されない。必要に応じて、自分で作成する。
例)app/Http/Controllers/Auth/LoginController.phpの作成の場合
% php artisan make:controller Auth\\LoginController
○ ビュー ( resources\views )
- Jetstreamが作成した、コンポーネントファイルは、隠しファイルになってる。 ↓のコマンドで表示でき、編集可能になる。
隠しファイルの表示コマンド
% php artisan vendor:publish --tag=jetstream-views
// resources/views/vendor/jetstream ディレクトリが出現する
ファイル名 | 用途 |
---|---|
auth\forgot-password.blade.php | PWリセットリンクの送信先入力フォーム |
auth\login.blade.php | ログインページ layouts\guest.blade.phpを読み込んでる。 |
auth\register.blade.php | 新規登録ページ layouts\guest.blade.phpを読み込んでる。 |
auth\reset-password.blade.php | PWリセット |
auth\two-factor-challenge.blade.php | ログインページ(2段階認証) |
auth\verify-email.blade.php | 本人確認ページ(2段階認証) |
layouts\app.blade.php | ログイン時のレイアウト (ナビゲージョンバー、ヘッダーを呼び出してる) |
layouts\guest.blade.php | 未ログイン時のレイアウト(ログイン・新規登録ページ) |
profile\●●.blade.php | プロフィール編集ページに使ってる部分テンプレート |
vendor\jetstream\components\●●.blade.php(隠しディレクトリ) | コンポーネントファイル(各種部分テンプレート) |
welcome.blade.php | welcome画面に認証用ボタンが設置される。 vendor\jetstream\components\welcome.blade.phpからコンポーネントを読み込んでる。 |
dashboard.blade.php | ダッシュボードページ layouts\app.blade.php、welcome.blade.phpを読み込んでる。 |
navigation-dropdown.blade.php | ナビゲージョンバー |
- @componentで呼び出していたコンポーネントを
<x-xxxx />
の形式で呼び出す仕組み。Livewireでは、全力でコンポーネントを使い倒してる。。-
x-
タグは、Bladeコンポーネント。x-jet-
タグは、Jetstream内で定義されてるコンポーネント。 - 例えば、
<x-app-layout>
は、app/View/Components/AppLayout.php を読み込んでる。
このファイルでは、resources/views/layouts/app.blade.php のビューを呼び出してるカタチになってる。 - 例えば、
<x-jet-welcome />
は、Jetstreamの隠しファイル内のresources/views/vender/jetstream/components/welcome.blade.php を読み込んでる。
-
○ モデル ( app\Models\User.php )
作成されたUserモデルの中身
// 値を代入するフィールドの設定
protected $fillable = ['name', 'email', 'password',];
// データを取得しないフィールドの指定
protected $hidden = ['password', 'remember_token', 'two_factor_recovery_codes', 'two_factor_secret',];
// キャストが必要なフィールドの指定
protected $casts = ['email_verified_at'=>'datetime',];
// モデルの配列に追加
protected $appends = ['profile_photo_url',];
○ 認証機能の設定 ( config\auth.php )
認証設定ファイル(config/auth.php)の中身
return [
'defaults' => [ // デフォルトの認証「ガード」と、PWリセットオプション
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false, // APIトークンを平文ではなく SHA-256 にする場合は true
],
],
'providers' => [
'users' => [
'driver' => 'eloquent', // データへのアクセスで使用するドライバー
'model' => App\Models\User::class, // 指定したドライバーが eloquent の場合、使うモデル
],
],
'passwords' => [
'users' => [
'provider' => 'users', // 使うproviderの設定
'table' => 'password_resets', // PWリセットトークンの格納テーブル
'expire' => 60, // 有効時間
'throttle' => 60, // 要求制限、スロットル時間(秒)
],
],
'password_timeout' => 10800, // PW再確認までの時間
];
○ プロバイダー ( app\Providers )
- Jetstream を導入すると、認証に関するFortify、Jetstream プロバイダーが追加される。
○ テーブル
- users テーブル : ユーザ管理用
カラム名 | カラム型 | 用途 |
---|---|---|
name | string | ユーザー名 |
string(一意性) | メールアドレス | |
email_verified_at | timestamp | メールによる本人確認日時 |
password | string | パスワード |
two_factor_secret | text(null可) | 2段階認証 |
two_factor_recovory_codes | text(null可) | リカバリーコード |
remember_token | remember_token | クッキー情報 |
current_team_id | foreignId(null可) | チーム機能で使用 |
profile_photo_path | text(null可) | プロフィールのアイコン写真のパス |
- password_resets テーブル : PWリセット管理用
カラム名 | カラム型 | 用途 |
---|---|---|
string(インデックス) | メールアドレス | |
token | string | PWリセットトークン |
- sessions テーブル : セッション管理用
カラム名 | カラム型 | 用途 |
---|---|---|
id | string(一意性) | ユーザー識別用のラベルみたいなヤツ |
user_id | foreignId(一意性、インデックス) | ユーザーid(外部キー) |
ip_address | string(null可) | アクセスしたIPアドレス |
user_agent | text(null可) | アクセスしたブラウザ情報 |
payload | text | Sessionに保存する値(base64でエンコードされてる) |
last_activity | integer(インデックス) | 最後のアクセス日時(unixタイムスタンプ) |
3. 例)基本の実装いろいろ
【 ログイン / アカウント登録後のリダイレクト先 の変更 】
- デフォルトだと、ログインやアカウント登録 → localhost:8000/dashboard にリダイレクトされる。
- 認証ユーザーリダイレクトの設定ファイル(app\Http\Middleware\RedirectIfAuthenticated.php) → 定義元(app\Providers\RouteServiceProvider.php)を呼び出してるカタチ。
app\Http\Middleware\RedirectIfAuthenticated.php
public function handle(Request $request, Closure $next, ...$guards) {
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME); // RouteServiceProvider を呼び出してる
}
}
return $next($request);
}
なので、定義元の方を変更してみる。
ログインやアカウント登録後のリダイレクト先の変更(app\Providers\RouteServiceProvider.php)
public const HOME = '/home'; // Jetstream導入前
public const HOME = '/dashboard'; // Jetstream導入後
↓ // rootページに変更
public const HOME = '/';
【 usersテーブルへのカラム追加 】
- 登録項目を追加してみる。
新規登録ページ | プロフィール編集ページ |
---|---|
localhost:8000/register | localhost:8000/user/profile |
- まずは、通常通り、マイグレーションファイルを作成し、usersテーブルにカラムを追加。
usersテーブルにカラム追加
% php artisan make:migration add_column_to_users_table --table=users
// マイグレーションファイルに、追加カラムを記述
public function up() {
Schema::table('users', function (Blueprint $table) {
$table->date('birth')->nullable();
});
}
public function down() {
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('birth');
});
}
% php artisan migrate // マイグレーション実行
次に、新規登録時に、データを保存できるよう、修正。
新規登録のための修正
// Userモデル
protected $fillable = [
:
'birth',
];
// app\Actions\Fortify\CreateNewUser.php
Validator::make($input, [
:
'birth' => ['nullable', 'date', 'before:yesterday'], // null or 昨日以前 でない場合は、バリデーション
])->validate();
return User::create([
:
'birth' => $input['birth'],
]);
// ビューに入力フォームを設置(resources\views\auth\register.blade.php)
<div class="mt-4">
<x-jet-label for="birth" value="{{ __('生年月日(※ 任意)') }}" />
<x-jet-input id="birth" class="block mt-1 w-full" type="date" name="birth" required />
</div>
- 同様に、プロフィール編集ページにも反映。
プロフィール編集ページの修正
// app\Actions\Fortify\UpdateUserProfileInformation.php
Validator::make($input, [
:
'birth' => ['required', 'date', 'before:yesterday'], // 削除メソッド未実装なので、とりあえず、空にできないよーにしとく。。
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email &&
:
} else {
$user->forceFill([
:
'birth' => $input['birth'],
])->save();
}
// ビュー(resources\views\profile\update-profile-information-form.blade.php)
<div class="col-span-6 sm:col-span-4">
<x-jet-label for="birth" value="{{ __('生年月日') }}" />
<x-jet-input id="birth" type="date" class="mt-1 block w-full" wire:model.defer="state.birth" />
<x-jet-input-error for="birth" class="mt-2" />
</div>
【 モデルの紐付け 】
- 例えば、記事投稿にユーザーを紐付ける場合。
- 紐付けたいテーブルに、user_idカラムを作成し、モデルにアソシエーションを追記。
Userモデルとのアソシエーション
public function user() {
return $this->belongsTo('App\Models\User');
}
コントローラーで取得して、ビューで表示してみる
// モデルを紐づけたので、コントローラーでは、Article::all()で、ユーザー情報も取得できてる
public function index() {
$articles = Article::all();
return view('index', ['articles' => $articles] );
}
// ビューで呼び出す
@foreach ($articles as $article)
<p>{{ $article -> user -> name }}</p> // 紐づくユーザー名 を表示
@endforeach
【 アクセス制限 】
- 例えば、投稿・編集・削除機能は、ログイン時のみに制限したい。
コントローラー
class ArticleController extends Controller {
// 未ログイン時は、index、showのみ許可する(それ以外では、未ログインなら、loginページへリダイレクト)
public function __construct() { // __construct クラスを追加
$this->middleware('auth')->except(['index', 'show']);
}
// 投稿機能
public function store(Request $request){
$article = new Article();
$user = \Auth::user();
$article -> content = request('content');
$article -> user_id = $user->id;
$article -> save();
return redirect() -> route('article.detail', ['article' => $article->id] );
}
// 投稿者のみ、詳細表示ページに、編集・削除ボタンを表示させたいので、ビューでの条件分岐のために、変数で定義
public function show($id){
$article = Article::find($id);
$user = \Auth::user();
if ($user) {
$login_user_id = $user->id;
} else {
$login_user_id = "";
}
return view('show', ['article' => $article, 'login_user_id' => $login_user_id] );
}
}
ビュー
// ログイン時のみ、登録ボタンを表示する
@auth
<div>
<a href='{{ route("article.new") }}'>登録ボタン</a>
</div>
@endauth
// 投稿者のみ、編集ボタン・削除ボタンを表示する
@auth
@if ($article -> user_id == $login_user_id) // 現在のユーザーid = 投稿者id の場合
<a href = '{{ route("article.edit", ["article" => $article->id]) }}', class='btn'>編集ボタン</a>
{{ Form::open(['method' => 'delete', 'route' => ['article.destroy', $article->id] ]) }}
{{ Form::submit('削除ボタン', ['class' => 'btn']) }}
{{ Form::close() }}
@endif
@endauth
- 動作確認用に、usersテーブルにダミーデータを作成しとくとイイかも。
ダミーデータの作成
// Seederに記述(database/seeders/DatabaseSeeder.php)
:
use App\Models\User; // Userモデルを使うよー
class DatabaseSeeder extends Seeder {
public function run() {
User::factory(5)->create(); // ダミーデータを5つ作成
}
}
// ダミーデータの作成実行(ターミナル)
% php artisan db:seed
【 Jetstream のロゴ変更 】
- ロゴを含んでるページは、ログインページ、新規登録ページ、ダッシュボードページなど。
ビュー | デフォルト | 変更後 |
---|---|---|
ログインページ | ||
新規登録ページ | ||
ダッシュボードページ | ||
プロフィール情報更新ページ |
- 今回は、FontAwesomeのアイコンを使うので、FontAwesome5を導入。ダウンロード or CDN リンクをビューファイルに貼りつける方法があるが、手っ取り早く、後者で。
参)FontAwesomeの導入方法
// layout.blade.php の <head>タグに記述
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">
// 使い方
<i class="fas fa-map-pin"></i>
■ ログイン、新規登録ページのロゴを変更する
- layout ファイルは、resources/views/layouts/guest.blade.php 。
- ロゴ部分のコードは、Jetstreamのコンポーネントにある(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php )。
- それぞれ、resources/views/auth/login.blade.php と register.blade.php で読み込んで使ってるので、コンポーネント側の記述を変更。
ログインページ、新規登録ページのロゴの変更
// FontAwesomeを使えるよーにする(resources/views/layouts/guest.blade.php の <head>タグ内に記述)
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">
// FontAwesomeのアイコンに変更してみる(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php)
<a href="/">
<h1>Lunch Map</h1>
<i class="fas fa-map-pin"></i>
</a>
■ ダッシュボード、プロフィール情報更新ページのロゴを変更する
- layout ファイルは、resources/views/layouts/app.blade.php 。
- ロゴ部分のコードは、Jetstreamのコンポーネントにある(resources/views/vendor/jetstream/components/authentication-card-logo.blade.php )。
- それぞれのページのヘッダー部分でナビゲージョンバー(resources/views/navigation-dropdown.blade.php) を読み込んでる。さらに、ナビゲージョンバーで、Jetstreamのコンポーネントを読み込んでる。
ダッシュボード、プロフィール情報更新ページのロゴの変更
// FontAwesomeを使えるよーにする(resources/views/layouts/app.blade.php の <head>タグ内に記述)
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css">
// (方法1) コンポーネントのロゴ部分を変更(resources/views/vendor/jetstream/components/application-mark.blade.php)
<i class="fas fa-map-pin fa-2x"></i>
// ヘッダーのナビゲーション部分(resources/views/navigation-dropdown.blade.php)
<a href="{{ route('article.list') }}"> // リンク先も自分の設定したページに変更(※ デフォルトは、ダッシュボード)
<x-jet-application-mark class="block h-9 w-auto" />
</a>
// (方法2) 若しくは、ナビゲーション部分を直接変更する(resources/views/navigation-dropdown.blade.php)
<a href="{{ route('article.list') }}">
<i class="fas fa-map-pin fa-2x"></i>
</a>
【 アイコン写真の登録 】
- Jetstream導入時に、デフォルトで、usersテーブルに写真データ用のカラムが作成されてる。
- Jeststream公式リファレンスを参考に、config/jetstream.php で、該当箇所のコメントアウトを外し、アイコン写真を有効化。
コメントアウトを外して有効化(config\jeststream.php)
'features' => [
Features::profilePhotos(),
Features::api(),
Features::teams(),
],
- 写真データの保存場所は、 public\strage\profile-photos 。
usersテーブルのprofile_photo_path カラムで、ココに格納されたデータを呼び出してる。
保存場所へシンボリックリンクを貼る必要がある
% php artisan storage:link
「.env」ファイルのURLも環境に合わせて変更(※ 今回はローカル環境)。
.envファイル
APP_URL=http://127.0.0.1:8000
(エラー) profile_photo_path カラムがないよ!
jetstream sqlstate[42s22]: column not found: 1054 unknown column 'profile_photo_path' in 'field list'
- (状況)今回、アプリ実装途中で、Jetstreamを導入した。既にマイグレーションを何度か実行しており、usersテーブルが作成されてる状態だった。
- Jetstream 導入によって、マイグレーションファイル(xxxxx_create_users_table.php)に、↓の記述が追加されるため、既に実行済みのマイグレーションファイルに追記されてしまい、カラムが作成できてなかったのが原因。
xxxxx_create_users_table.php
// Jetstream 導入で、追加されたカラム
$table->foreignId('current_team_id')->nullable();
$table->text('profile_photo_path')->nullable();
- なので、マイグレーション 初期化 → 再実行で、profile_photo_path カラムが作成でき、解決。
マイグレーションを初期化→再実行
% php artisan migrate:refresh