初期状態のLaravelには何も入っていません。
ログインを使えるようにカスタマイズする必要があります。
ここではLaravelはインストールした状態でVSCodeでこのLaravelプロジェクト(laravelフォルダ)を読み込んだ状態から始めます。
Laravel のインストール
環境設定をする
まず、ENVファイルとapp.phpの設定をします。
ENVファイルは、サイト名・データベース名・送信メールサーバーを設定します。
XAMPPのコントロールパネルでApacheとMySQLをスタートし、PHPMyAdminで、このプロジェクト用のデータベースを作成します。
作成したデータベースの情報をENVに入力します。
//ENV
APP_NAME=Laravel
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
app.php では、使用環境の設定を行います。
app.php のenv() 関数は、基本的にENVファイルを参照し、ENVに記載が無ければ、第2引数の値を取ります。
下記の項目を日本対応にしてください。
//config\app.php
'timezone' => 'UTC', //Asia/Tokyo の日本時間に変更
'locale' => 'en', //ja に変更
'faker_locale' => 'en_US', //ja_JP に変更
あと、データベースの文字コードが utf8mb4 である場合、次に行うデータベースのマイグレーションでエラーが出る場合があるので、AppServiceProvider.php に下記のコードを追加します。
//App\Providers\AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; //追加
public function boot()
{
Schema::defaultStringLength(191);
}
次に、サーバーを立ち上げたとき、ページに変更したタイトルが出て、設定を変更したのがわかりやすいように、少し変更します。
titleタグにLaravel と書かれているところを下記に変更します。
//resouces\views\welcome.blade.php
<title>{{ config('app.name') }}</title>
bodyタグの直下にも下記を追加します。
<h1 style="text-align:center;">{{ config('app.name') }}</h1>
ENVファイルやapp.phpなど、設定項目を変更したので、キャッシュをクリアします。
VSCodeでターミナルを立ち上げて、下記のコードを入力してEnterを押します。
php artisan config:cache
ここでサーバーを立ち上げてみます。
続けてターミナルに、下記のコードを入力します。
php artisan serve
ブラウザで確認します。ブラウザのURLに下記の文字列を入力します。
http://localhost:8000
矢印の部分が、自分の設定にした名称になっていれば成功です。
先ほど artisan したターミナルで、キーボードのCtrlとCを同時押しして、サーバーを停止します。
Auth認証 Laravel Breeze のインストール
Laravel Breeze は、ログイン・ユーザー登録・パスワードのリセットなどの、Auth認証をシンプルに実装したパッケージです。
Bladeファイルは、Tailwind CSSでスタイルが設定されています。
※VueやReactが分かる人であれば、Inertia.jsのフロント実装の提供もあるので、インストール時にVueかReactを指定して使うこともできます。
Breezeは、Composerでインストールします。
ターミナルで下記のコードを入力します。
composer require laravel/breeze --dev
次に下記のコードを入力します。
php artisan breeze:install
インストールできたから、「npm インストールしてね」とアセットのコンパイルを求めております。
なので、次のコードを入力します。
npm install
こんな状態で1分~数分、待たされますが、じっと待ちます。(通信環境次第)
終わったら、次のコードを入力します。
npm run dev
あらま、怒られました。
hide moduleなんて知らんがな と言っています。
書かれている通り、–hide-modules の記載のある場所を、VSCode検索で探します。
①のマークをクリックして、②に「–hide-modules」と入力します。
package.json の中で2か所見つかったので、その部分を消します。
もう一度、先ほどのコードを入力します。
npm run dev
今度は無事コンパイル成功で、右下にLaravel Mix Build と出ますので、これをタッチします。
先ほどまで、resources\views の中には、welcome.blade.php しかなかったのが、増えています。
これらは全て、ログイン用のテンプレートです。
コントローラー側でもAuthフォルダが作成され、専用のコントローラーが増えています。
データベースのマイグレーション
ユーザー登録でデータベースを利用するので、データベースにテーブルを作成します。
先ほどのBreezeのインストールで、databaseフォルダにユーザーテーブルを作るphpができているので、これを確認します。
場所は、database\xxxx_xxxx_xxxxx_create_users_table.php です。
必要に応じて、カラムを追加します。
今回は、stringのnicknameカラムを追加してみました。
Laravelのデータベースにはいくつかお約束があります。
- テーブル名は小文字の複数形
- カラム名は小文字で単数形
- 単語+単語の名前であるなら、スネーク記法(アンダースコア_でつなぐ)
- 日時はat (create_at など)
- リレーション先のidをカラム名にするときは、単数形_id にする。(usersテーブルのidならuser_id)
カラムタイプは以下のページに詳細があります。
利用可能なカラムタイプ
Laravel 8.x マイグレーション
確認が終わったら、ターミナルに下記のコードを入力してマイグレーションします。
php artisan migrate
①にあるphpファイルが一斉にマイグレーションされ、データベースの中にテーブルが生成されます。
確認してみましょう。
XamppコントロールパネルでPHPMyAdminにログインして、このプロジェクトのデータベースを確認します。
databaseフォルダにあるphpと同じ、failed_jobs、password_resets、usersのテーブルが作成され、その情報が、migrationsテーブルの中に記載されています。
ここで、サーバーを立ち上げてみます。
php artisan serve
画面の右上にLoginとRegistar のリンクができています。
Loginをクリックします。
EmailとPasswordでログインする形式のフォームが表示されます。
Foget~でパスワードを忘れた人用のリンクもあり、Remember meで記憶することもできるようです。
Registarをクリックします。
名前とメールアドレス、パスワード、確認パスワードで登録するフォームです。
登録済みの場合は、ログインページに飛ばすリンクもついています。
ただ、このままでは表示が全部英語なのと、下記のようにエラーも英語なので、これを日本語にしたいと思います。
いったんサーバーを落とします。
ターミナルでCtrl+Cを押します。
フォームの日本語化
resourcesファイルの中に、langファイルがあります。
この中のenフォルダを丸ごとコピーして、jaとリネームします。
そして各英語を日本語に翻訳します。
あと、langフォルダの直下にja.jsonを作成し、以下の内容をコピーします。
{
"Register": "登録",
"Log in": "ログイン",
"Log Out": "ログアウト",
"Login": "ログイン",
"Logout": "ログアウト",
"Password": "パスワード",
"Email": "メールアドレス",
"Email Address": "メールアドレス",
"Email Addresses": "メールアドレス",
"Email Password Reset Link": "送信",
"Forgot your password?": "パスワードをお忘れですか?",
"Remember me": "ログイン状態を保持する",
"Remember Me": "ログイン状態を保持する",
"Name": "名前",
"NickName": "ニックネーム",
"Gender": "性別",
"Confirm Password": "パスワードの確認",
"Already registered?": "登録済ですか?"
}
//auth.phpの翻訳
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => '登録情報と一致しません。',
'password' => 'パスワードが正しくありません。',
'throttle' => 'ログイン試行回数が多すぎます。 :seconds 秒後に再試行してください。',
];
//pagination.php の翻訳
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '« 前',
'next' => '次 »',
];
//passwords.php の翻訳
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'reset' => 'パスワードはリセットされました!',
'sent' => 'パスワードリセットリンクをメールで送信しました!',
'throttled' => '時間を置いて再度お試しください。',
'token' => 'このパスワードリセットトークンは無効です。',
'user' => "そのメールアドレスのユーザーが見つかりません。",
];
//validation.php の翻訳
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => ':attributeを承認してください。',
'accepted_if' => 'The :attribute must be accepted when :other is :value.',
'active_url' => ':attributeは、有効なURLではありません。',
'after' => ':attributeには、:dateより後の日付を指定してください。',
'after_or_equal' => ':attributeには、:date以降の日付を指定してください。',
'alpha' => ':attributeには、アルファベッドのみ使用できます。',
'alpha_dash' => ':attributeには、英数字(\'A-Z\',\'a-z\',\'0-9\')とハイフンと下線(\'-\',\'_\')が使用できます。',
'alpha_num' => ':attributeには、英数字(\'A-Z\',\'a-z\',\'0-9\')が使用できます。',
'array' => ':attributeには、配列を指定してください。',
'attached' => 'この:attributeはすでに添付されています。',
'before' => ':attributeには、:dateより前の日付を指定してください。',
'before_or_equal' => ':attributeには、:date以前の日付を指定してください。',
'between' => [
'array' => ':attributeの項目は、:min個から:max個にしてください。',
'file' => ':attributeには、:min KBから:max KBまでのサイズのファイルを指定してください。',
'numeric' => ':attributeには、:minから、:maxまでの数字を指定してください。',
'string' => ':attributeは、:min文字から:max文字にしてください。',
],
'boolean' => ':attributeには、\'true\'か\'false\'を指定してください。',
'confirmed' => ':attributeと:attribute確認が一致しません。',
'current_password' => 'The password is incorrect.',
'date' => ':attributeは、正しい日付ではありません。',
'date_equals' => ':attributeは:dateに等しい日付でなければなりません。',
'date_format' => ':attributeの形式は、\':format\'と合いません。',
'different' => ':attributeと:otherには、異なるものを指定してください。',
'digits' => ':attributeは、:digits桁にしてください。',
'digits_between' => ':attributeは、:min桁から:max桁にしてください。',
'dimensions' => ':attributeの画像サイズが無効です',
'distinct' => ':attributeの値が重複しています。',
'email' => ':attributeは、有効なメールアドレス形式で指定してください。',
'ends_with' => ':attributeは、次のうちのいずれかで終わらなければなりません。: :values',
'exists' => '選択された:attributeは、有効ではありません。',
'file' => ':attributeはファイルでなければいけません。',
'filled' => ':attributeは必須です。',
'gt' => [
'array' => ':attributeの項目数は、:value個より大きくなければなりません。',
'file' => ':attributeは、:value KBより大きくなければなりません。',
'numeric' => ':attributeは、:valueより大きくなければなりません。',
'string' => ':attributeは、:value文字より大きくなければなりません。',
],
'gte' => [
'array' => ':attributeの項目数は、:value個以上でなければなりません。',
'file' => ':attributeは、:value KB以上でなければなりません。',
'numeric' => ':attributeは、:value以上でなければなりません。',
'string' => ':attributeは、:value文字以上でなければなりません。',
],
'image' => ':attributeには、画像を指定してください。',
'in' => '選択された:attributeは、有効ではありません。',
'in_array' => ':attributeが:otherに存在しません。',
'integer' => ':attributeには、整数を指定してください。',
'ip' => ':attributeには、有効なIPアドレスを指定してください。',
'ipv4' => ':attributeはIPv4アドレスを指定してください。',
'ipv6' => ':attributeはIPv6アドレスを指定してください。',
'json' => ':attributeには、有効なJSON文字列を指定してください。',
'lt' => [
'array' => ':attributeの項目数は、:value個より小さくなければなりません。',
'file' => ':attributeは、:value KBより小さくなければなりません。',
'numeric' => ':attributeは、:valueより小さくなければなりません。',
'string' => ':attributeは、:value文字より小さくなければなりません。',
],
'lte' => [
'array' => ':attributeの項目数は、:value個以下でなければなりません。',
'file' => ':attributeは、:value KB以下でなければなりません。',
'numeric' => ':attributeは、:value以下でなければなりません。',
'string' => ':attributeは、:value文字以下でなければなりません。',
],
'max' => [
'array' => ':attributeの項目は、:max個以下にしてください。',
'file' => ':attributeには、:max KB以下のファイルを指定してください。',
'numeric' => ':attributeには、:max以下の数字を指定してください。',
'string' => ':attributeは、:max文字以下にしてください。',
],
'mimes' => ':attributeには、:valuesタイプのファイルを指定してください。',
'mimetypes' => ':attributeには、:valuesタイプのファイルを指定してください。',
'min' => [
'array' => ':attributeの項目は、:min個以上にしてください。',
'file' => ':attributeには、:min KB以上のファイルを指定してください。',
'numeric' => ':attributeには、:min以上の数字を指定してください。',
'string' => ':attributeは、:min文字以上にしてください。',
],
'multiple_of' => ':attributeは:valueの倍数でなければなりません',
'not_in' => '選択された:attributeは、有効ではありません。',
'not_regex' => ':attributeの形式が無効です。',
'numeric' => ':attributeには、数字を指定してください。',
'password' => 'パスワードが正しくありません。',
'present' => ':attributeが存在している必要があります。',
'prohibited' => ':attributeフィールドは禁止されています。',
'prohibited_if' => ':attributeフィールドは、:otherが:valueの場合は禁止されています。',
'prohibited_unless' => ':attributeフィールドは、:otherが:valuesでない限り禁止されています。',
'prohibits' => 'The :attribute field prohibits :other from being present.',
'regex' => ':attributeには、有効な正規表現を指定してください。',
'relatable' => 'この:attributeきない場合に伴い資源です。',
'required' => ':attributeは、必ず指定してください。',
'required_if' => ':otherが:valueの場合、:attributeを指定してください。',
'required_unless' => ':otherが:values以外の場合、:attributeを指定してください。',
'required_with' => ':valuesが指定されている場合、:attributeも指定してください。',
'required_with_all' => ':valuesが全て指定されている場合、:attributeも指定してください。',
'required_without' => ':valuesが指定されていない場合、:attributeを指定してください。',
'required_without_all' => ':valuesが全て指定されていない場合、:attributeを指定してください。',
'same' => ':attributeと:otherが一致しません。',
'size' => [
'array' => ':attributeの項目は、:size個にしてください。',
'file' => ':attributeには、:size KBのファイルを指定してください。',
'numeric' => ':attributeには、:sizeを指定してください。',
'string' => ':attributeは、:size文字にしてください。',
],
'starts_with' => ':attributeは、次のいずれかで始まる必要があります。:values',
'string' => ':attributeには、文字を指定してください。',
'timezone' => ':attributeには、有効なタイムゾーンを指定してください。',
'unique' => '指定の:attributeは既に使用されています。',
'uploaded' => ':attributeのアップロードに失敗しました。',
'url' => ':attributeは、有効なURL形式で指定してください。',
'uuid' => ':attributeは、有効なUUIDでなければなりません。',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [
'name' => '名前',
'email' => 'メールアドレス',
'password' => 'パスワード',
],
];
これでいったん、サーバーを立ち上げてみます。
php artisan serve
右上のRegistarリンクをクリックします。
このように表示が日本語になっていれば成功です。
日本語になっていない場合は、まず上の修正を全て確認します。
きちんとできていれば、次にターミナルでCtrl+Cを押します。
さらに次のコードでキャッシュをクリアします。
php artisan config:cache
それからサーバーを起動します。
php artisan serve
ユーザー登録時にカラムを追加した場合
現在のフォームは、名前とメールアドレスとパスワードで、ユーザー登録することになっています。
先ほど、ユーザーテーブル作成で、ニックネームを追加したので、これをフォームに追加したいと思います。
あと、追加で性別が欲しくなったのでフォームに追加した後、性別カラムをデータベースに追加する必要があります。
resourcesフォルダのviewsフォルダの中にauthフォルダがあります。
これがログイン・登録用のテンプレートがあるフォルダです。
登録時のフォームを変更したいので、register.blade.phpを変更します。
nameのインプット周りをコピーして、Nicknameにリネームします。
またgenderも追加で入れ、requiredにします。
//register.blade.php
<x-guest-layout>
<x-auth-card>
<x-slot name="logo">
<a href="/">
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
</a>
<h1>新規登録</h1><!-- Form名がわかるように追加(任意) -->
</x-slot>
<!-- Validation Errors -->
<x-auth-validation-errors class="mb-4" :errors="$errors" />
<form method="POST" action="{{ route('register') }}">
@csrf
<!-- Gender追加 デフォルトで男性にチェック -->
<div class="mt-1">
<x-label for="gender" :value="__('Gender')" />
<div class="flex">
<div class="mr-2 flex items-center">
<x-input id="gender" class="mt-1 mr-2 appearance-none checked:bg-blue-500" type="radio" name="gender" :value="1" required autofocus checked aria-checked="true" /> <span>男性</span>
</div>
<div class="flex items-center">
<x-input id="gender" class="mt-1 mr-2 appearance-none checked:bg-blue-500" type="radio" name="gender" :value="2" required autofocus /> <span>女性</span>
</div>
</div>
</div>
<!-- Name -->
<div class="mt-4">
<x-label for="name" :value="__('Name')" />
<x-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus />
</div>
<!-- NickName追加 -->
<div class="mt-4">
<x-label for="nickname" :value="__('NickName')" />
<x-input id="nickname" class="block mt-1 w-full" type="text" name="nickname" :value="old('nickname')" required autofocus />
</div>
<!-- Email Address -->
<div class="mt-4">
<x-label for="email" :value="__('Email')" />
<x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required />
</div>
<!-- Password -->
<div class="mt-4">
<x-label for="password" :value="__('Password')" />
<x-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="new-password" />
</div>
<!-- Confirm Password -->
<div class="mt-4">
<x-label for="password_confirmation" :value="__('Confirm Password')" />
<x-input id="password_confirmation" class="block mt-1 w-full"
type="password"
name="password_confirmation" required />
</div>
<div class="flex items-center justify-end mt-4">
<a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('login') }}">
{{ __('Already registered?') }}
</a>
<x-button class="ml-4">
{{ __('Register') }}
</x-button>
</div>
</form>
</x-auth-card>
</x-guest-layout>
このまま新規登録ボタンを押しても、データベースにカラムは無いし、登録フォームのPOST部分も修正していないのでエラーが出るだけです。
まず、POST部分の修正から行います。
RegisterControllerの修正
register.blade.phpのフォームのフォームタグ部分に注目します。
<form method="POST" action="{{ route('register') }}">
POST先が、route(‘register)と書かれています。
route()の移動先は、基本的に、routesフォルダのweb.phpに書かれています。
//routes\web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
require __DIR__.'/auth.php';
ここには、/とdashboardとauth.phpと書かれています。
なので、auth.phpを見ます。
viewsでもあった通り、authのフォルダの中の移動先は、基本的にauth.phpに書かれていると思ってよいです。
//routes\auth.php
<?php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\ConfirmablePasswordController;
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\Auth\EmailVerificationPromptController;
use App\Http\Controllers\Auth\NewPasswordController;
use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])
->name('password.update');
});
Route::middleware('auth')->group(function () {
Route::get('verify-email', [EmailVerificationPromptController::class, '__invoke'])
->name('verification.notice');
Route::get('verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');
Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
->name('password.confirm');
Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
->name('logout');
});
POSTやGETに使用するコントローラーの名前とRoute名が書かれています。
今回は、POSTで、route(‘register)を探しているので、Route::post(‘register’)の項目を見ます。
Route::post('register', [RegisteredUserController::class, 'store']);
RegisteredUserControllerのstoreファンクションだよと書かれています。
場所は、useに書かれている、appフォルダのHttpフォルダの中、Controllersフォルダの中のAuthフォルダの中に、RegisteredUserControllerは存在します。
VSCodeで開きます。
createファンクションを見ると、authフォルダのregister.blade.phpを見てねと書かれています。
目的は「store」ファンクションなので、確認します。
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
まず、POSTされたら、POSTされた、nameとemailとpasswordをバリデーションします。
これでエラーがあれば、戻されます。
エラーが無ければ、Userモデルに従って、usersテーブルに新規データをPOSTされた内容で作成します。
その後、Auth認証し、RouteServiceProviderクラスでHOMEに指定された場所(下記)にリダイレクトします。
//app\Providers\RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
public const HOME = '/dashboard';
}
ここで、RegisteredUserController.phpにフォームで追加したカラムの処理を追加していきます。
環境設定をしたときに、AppServiceProvider.phpでMAXの値を変更したので、バリデーションの各stringのMAXの値は、max:190に直します。
//app\Http\Controllers\Auth\RegisteredUserController.php
$request->validate([
'name' => ['required', 'string', 'max:190'],
'email' => ['required', 'string', 'email', 'max:190', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
'gender' => ['required', 'integer'],
'nickname' => ['required', 'string', 'max:190'],
]);
データーベースのusersテーブルに追加する内容を追加します。
$user = User::create([
'gender' => $request->gender,
'nickname' => $request->nickname,
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
これはUserモデルでcreateしてねという意味です。
Userモデルを確認します。
appフォルダのModelsフォルダの中にUser.phpがあります。
基本的に、モデルはデータベースのテーブルと連動します。
モデルを作成する際、一緒にmigrationすると、Modelsフォルダにモデル(Admin.php)が、そのmigrationファイル(create_admins_table.php)が、databaseフォルダの中に作成できます。
//モデルを作成するとき--migrationでデータベースのmigrationファイルも作成
php artisan make:model Admin --migration
//Userモデル app\Models\User.php
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
【説明】
use HasFactory, Notifiable;
HasFactoryを使うよ、Notifiableを使うよと宣言。
HasFactory / Notifiableは、上のnamespaceの下に書かれている。
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;
VSCodeであれば、上のHasFactoryの文字列が緑になっていて、マウスでタッチすると内容が見れるはずなので割愛します。
簡単に言うと、Factory, Notifiableを使ってデータ入れるよってことです。
protected $fillable = [
'name', 'email', 'password', 'gender', 'nickname'
];
配列fillableでは、フォームの登録・更新の際に使用するカラムを指定します。
フォームにgenderとnicknameを追加したので、ここにも追加します。
protected $hidden = [
'password', 'remember_token',
];
変数hiddenでは、モデルクラスのクエリ結果で、ここで指定したカラムを非表示にします。
protected $casts = [
'email_verified_at' => 'datetime',
];
変数castでは ここで指定したカラムの型をキャストします。
Modelでは、他にリレーションの関係なども書くことができます。
一対一、一対他、他対多などの関係があります。
詳しくは、Laravel8.x Eloquent リレーション を見てください。
登録時のカラムをデータベースにも追加します。
ターミナルに下記を入力します。
php artisan make:migration add_gender_to_users_table --table=users
これは、usersテーブルに、genderカラムを追加するmigrationファイルを作る構文です。
databaseフォルダに、migrationファイルができているはずです。
このadd_gender_to_users_table.phpファイルを編集します。
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->integer('gender');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('gender');
});
}
up()はカラムの追加で、down()がカラムの削除です。
integerでカラムの型を数値型に指定しました。
編集したので、ターミナルに下記のコードを入力します。
まだマイグレーションされていない新しいファイルがmigrationされます。
php artisan migrate
修正したので、サーバーを立ち上げて確認します。
php artisan serve
新規登録ボタンを押します。
おっと、パスワードは8文字にしろと怒られました。ちゃんとエラーは日本語になっているようです。
しかし、その上の文字が英語なので、そこを直します。
resouces\langに作った、ja.json に次の文を追加します。
"Whoops! Something went wrong.": "何らかのエラーがあります。"
わざとエラーを出しましたが、ちゃんと日本語になりました。
では、きちんと登録してみます。
データが保存され、ログインできました。
データベースの中も確認します。XamppのコントロールパネルのMySQLのAdminボタンから、PHPMyAdminを開きます。
登録データがしっかり入っています。
登録時のパスワードに変更を入れる方法
現在、登録時のパスワードは、8文字の英数字になっています。
最近は、セキュリティ対策として、大文字小文字数字の全種類を最低1文字以上使って、という登録方法が増えてきています。
それに対応してみたいと思います。
Laravelでは、パスワード生成に関するクラスが最初からあるので、それを利用します。
Illuminate\Validation\Rulesの中で、パスワードに関するルールを設定しています。
これを登録時のバリデーションに追加することで、使うことができるようになります。
//Password.php
namespace Illuminate\Validation\Rules;
use Illuminate\Container\Container;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Traits\Conditionable;
use InvalidArgumentException;
class Password implements Rule, DataAwareRule, ValidatorAwareRule
{
use Conditionable;
/**
* The validator performing the validation.
*
* @var \Illuminate\Contracts\Validation\Validator
*/
protected $validator;
/**
* The data under validation.
*
* @var array
*/
protected $data;
/**
* The minimum size of the password.
*
* @var int
*/
protected $min = 8;
/**
* If the password requires at least one uppercase and one lowercase letter.
*
* @var bool
*/
protected $mixedCase = false;
/**
* If the password requires at least one letter.
*
* @var bool
*/
protected $letters = false;
/**
* If the password requires at least one number.
*
* @var bool
*/
protected $numbers = false;
/**
* If the password requires at least one symbol.
*
* @var bool
*/
protected $symbols = false;
/**
* If the password should has not been compromised in data leaks.
*
* @var bool
*/
protected $uncompromised = false;
/**
* The number of times a password can appear in data leaks before being consider compromised.
*
* @var int
*/
protected $compromisedThreshold = 0;
/**
* Additional validation rules that should be merged into the default rules during validation.
*
* @var array
*/
protected $customRules = [];
/**
* The failure messages, if any.
*
* @var array
*/
protected $messages = [];
/**
* The callback that will generate the "default" version of the password rule.
*
* @var string|array|callable|null
*/
public static $defaultCallback;
/**
* Create a new rule instance.
*
* @param int $min
* @return void
*/
public function __construct($min)
{
$this->min = max((int) $min, 1);
}
/**
* Set the default callback to be used for determining a password's default rules.
*
* If no arguments are passed, the default password rule configuration will be returned.
*
* @param static|callable|null $callback
* @return static|null
*/
public static function defaults($callback = null)
{
if (is_null($callback)) {
return static::default();
}
if (! is_callable($callback) && ! $callback instanceof static) {
throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
}
static::$defaultCallback = $callback;
}
/**
* Get the default configuration of the password rule.
*
* @return static
*/
public static function default()
{
$password = is_callable(static::$defaultCallback)
? call_user_func(static::$defaultCallback)
: static::$defaultCallback;
return $password instanceof Rule ? $password : static::min(8);
}
/**
* Get the default configuration of the password rule and mark the field as required.
*
* @return array
*/
public static function required()
{
return ['required', static::default()];
}
/**
* Get the default configuration of the password rule and mark the field as sometimes being required.
*
* @return array
*/
public static function sometimes()
{
return ['sometimes', static::default()];
}
/**
* Set the performing validator.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return $this
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* Set the data under validation.
*
* @param array $data
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Sets the minimum size of the password.
*
* @param int $size
* @return $this
*/
public static function min($size)
{
return new static($size);
}
/**
* Ensures the password has not been compromised in data leaks.
*
* @param int $threshold
* @return $this
*/
public function uncompromised($threshold = 0)
{
$this->uncompromised = true;
$this->compromisedThreshold = $threshold;
return $this;
}
/**
* Makes the password require at least one uppercase and one lowercase letter.
*
* @return $this
*/
public function mixedCase()
{
$this->mixedCase = true;
return $this;
}
/**
* Makes the password require at least one letter.
*
* @return $this
*/
public function letters()
{
$this->letters = true;
return $this;
}
/**
* Makes the password require at least one number.
*
* @return $this
*/
public function numbers()
{
$this->numbers = true;
return $this;
}
/**
* Makes the password require at least one symbol.
*
* @return $this
*/
public function symbols()
{
$this->symbols = true;
return $this;
}
/**
* Specify additional validation rules that should be merged with the default rules during validation.
*
* @param string|array $rules
* @return $this
*/
public function rules($rules)
{
$this->customRules = Arr::wrap($rules);
return $this;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$this->messages = [];
$validator = Validator::make(
$this->data,
[$attribute => array_merge(['string', 'min:'.$this->min], $this->customRules)],
$this->validator->customMessages,
$this->validator->customAttributes
)->after(function ($validator) use ($attribute, $value) {
if (! is_string($value)) {
return;
}
$value = (string) $value;
if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
$validator->errors()->add($attribute, 'The :attribute must contain at least one uppercase and one lowercase letter.');
}
if ($this->letters && ! preg_match('/\pL/u', $value)) {
$validator->errors()->add($attribute, 'The :attribute must contain at least one letter.');
}
if ($this->symbols && ! preg_match('/\p{Z}|\p{S}|\p{P}/u', $value)) {
$validator->errors()->add($attribute, 'The :attribute must contain at least one symbol.');
}
if ($this->numbers && ! preg_match('/\pN/u', $value)) {
$validator->errors()->add($attribute, 'The :attribute must contain at least one number.');
}
});
if ($validator->fails()) {
return $this->fail($validator->messages()->all());
}
if ($this->uncompromised && ! Container::getInstance()->make(UncompromisedVerifier::class)->verify([
'value' => $value,
'threshold' => $this->compromisedThreshold,
])) {
return $this->fail(
'The given :attribute has appeared in a data leak. Please choose a different :attribute.'
);
}
return true;
}
/**
* Get the validation error message.
*
* @return array
*/
public function message()
{
return $this->messages;
}
/**
* Adds the given failures, and return false.
*
* @param array|string $messages
* @return bool
*/
protected function fail($messages)
{
$messages = collect(Arr::wrap($messages))->map(function ($message) {
return $this->validator->getTranslator()->get($message);
})->all();
$this->messages = array_merge($this->messages, $messages);
return false;
}
}
上記のコードの中の、mixedCaseで最低1文字の大文字と小文字が必須、numbersで最低1文字の数字が必須、minでパスワードの長さが指定できます。
登録時のバリデーションを行う、RegisteredUserController.phpを修正します。
//RegisteredUserController.php
$request->validate([
'gender' => ['required', 'integer'],
'nickname' => ['required', 'string', 'max:190'],
'name' => ['required', 'string', 'max:190'],
'email' => ['required', 'string', 'email', 'max:190','unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()->mixedCase()->numbers()],
]);
これで、大文字が最低1文字・小文字が最低1文字・数字が最低1文字の合計8文字以上のパスワード文字列が必須という意味のルールができました。
登録フォーム(resources\views\auth\register.blade.php)にその旨を記載します。
<!-- Password -->
<div class="mt-4">
<x-label for="password" :value="__('Password')" />
<x-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="new-password" />
<span>パスワードは、大文字・小文字・数字をそれぞれ1文字以上含めた8文字以上にしてください。</span>
</div>
password.phpに書かれているエラー内容の日本語も、ja.jsonに追加します。
"The :attribute must be at least :length characters.": ":attributeは:length文字以上でなければなりません。",
"The :attribute must contain at least one number.": ":attributeは数字を1文字以上含めなければなりません。",
"The :attribute must contain at least one uppercase and one lowercase letter.": ":attributeは大文字と小文字をそれぞれ1文字以上含めなければなりません。"
バリデーションを追加した部分もきちんと日本語になりました。
登録時に、メール認証を追加する方法
Laravelのユーザー登録は、初期状態では、登録してそのままログインとなります。
これをセキュリティ強化のため、仮登録→メール認証→認可→ログインの形に変更したいと思います。
最初に、Userモデルを変更します。
Authenticatable の文字の横に追加します。これで、登録した後、ユーザーに確認のリンクが記載されたメールが送信されます。
//app\Models\User.php
//class User extends Authenticatable
class User extends Authenticatable implements MustVerifyEmail
{
}
登録後、メール認証されてないユーザーのログインを防ぐため、ルーティングファイルを修正します。
routes\web.php を修正します。
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');//middleware(['auth'])
登録してみます。
送信すると、ログインしましたと書かれたダッシュボードではなく、次の画面になります。
「サインアップしていただきありがとうございます! 始める前に、メールで送信したリンクをクリックして、メールアドレスを確認していただけますか? メールが届かない場合は、別のメールをお送りします。」と書かれています。送信されたメールをMailtrapで確認します。
リンクのあるメールが送信されていました。
ボタンをクリックしてメールを確認してくださいとあります。
真ん中の黒ボタン、「Verify Email Address」をクリックすると認証が始まり、成功すれば、ログインフォーム画面に移動します。
ボタンがうまく動作しないとき、表示されないときは、下のほうに書かれているリンクをクリックすると同じ動作を行います。
ログインフォームで、先ほど登録したメールアドレスとパスワードでログインします。
今度は、認証が済んでいるので、ダッシュボードに移動します。
データベースのusersテーブルにも認証済みの日付が入っています。
認証案内ページと、メール認証のメール文を日本語にする
認証の案内のページのURLは、verify-emailです。
routes/web.php から探しますが、トップページとダッシュボード以外は、auth.phpにあるようなので、auth.phpを開きます。
Route::middleware('auth')->group(function () {
Route::get('verify-email', [EmailVerificationPromptController::class, '__invoke'])
->name('verification.notice');
}
verify-email は、EmailVerificationPromptController.phpで定義してるようなので、そちらを見ます。
//app\Http\Controllers\Auth\EmailVerificationPromptController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(RouteServiceProvider::HOME)
: view('auth.verify-email');
}
}
これを見ると、auth.verify-email.blade.php を表示するといっているので、verify-email.blade.phpを見つけます。
//resources\views\auth\verify-email.blade.php
<x-guest-layout>
<x-auth-card>
<x-slot name="logo">
<a href="/">
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
</a>
</x-slot>
<div class="mb-4 text-sm text-gray-600">
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
</div>
@if (session('status') == 'verification-link-sent')
<div class="mb-4 font-medium text-sm text-green-600">
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
</div>
@endif
<div class="mt-4 flex items-center justify-between">
<form method="POST" action="{{ route('verification.send') }}">
@csrf
<div>
<x-button>
{{ __('Resend Verification Email') }}
</x-button>
</div>
</form>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900">
{{ __('Log Out') }}
</button>
</form>
</div>
</x-auth-card>
</x-guest-layout>
{{ __() }} で生成されている部分は、全言語共通部分なので、シングルクォーテーションの中身をコピーして、ja.jsonに翻訳を追加して書けば日本語化ができます。
"Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.": "ご登録ありがとうございます。入力いただいたメールアドレス宛にを確認のメールを送信しました。メールをご確認いただき、メールに記載されたURLをクリックして登録を完了してください。メールが届かない場合、メールを再送できます。",
"Resend Verification Email": "確認メールを再送する"
次は、送信されるメール認証のメール文を日本語化します。
案内ページ auth.verify-email.blade.phpのフォームのPOST部分に注目します。
<form method="POST" action="{{ route('verification.send') }}">
routes\auth.phpで nameがverification.sendになっているものを探します。
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');
EmailVerificationNotificationController に内容が書かれているようなので調べます。
//App\Http\Controllers\Auth\EmailVerificationNotificationController.php
public function store(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
【説明】
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
hasVerifiedEmail()で、usersテーブルのemail_verified_atの値がNULLではない。という意味です。
なので、リクエストされたユーザーのemail_verified_atの値がNULLではないので、ダッシュボードにリダイレクトします。となります。
$request->user()->sendEmailVerificationNotification();
このsendEmailVerificationNotificationの内容は、VerifyEmail.phpに書かれています。
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject(Lang::get('Verify Email Address'))
->line(Lang::get('Please click the button below to verify your email address.'))
->action(Lang::get('Verify Email Address'), $url)
->line(Lang::get('If you did not create an account, no further action is required.'));
}
このLang::getの中身をja.jsonに追加して翻訳すれば日本語になります。
"Please click the button below to verify your email address.": "メールアドレスを確認してアカウントを有効にするには、以下のボタンをクリックしてください。",
"Verify Email Address": "メールアドレスを確認する",
"If you did not create an account, no further action is required.": "アカウントの作成にお心当たりがない場合は、このメールを無視してください。",
"Regards": "よろしくお願いします"
Laravelのたくさんのメッセージが日本語化された ja.jsonは、Githubにあります。
ある程度、Laravelになれたら、たくさん記載された ja.jsonをダウンロードして使うと良いと思います。
Laravel言語ファイル