Laravelのユーザー登録機能は、名前、メールアドレス、パスワードしか入力項目がありません。最低限の項目だけです。
実際のアプリケーションでは、その他の項目も必要になります。
- ECサイトであれば、住所や電話番号
- 社内システムであれば、社員番号や部署
こんな感じですね。
ということで、ユーザー登録機能に項目を追加してみます。
試したときの環境
- macOS 11.2.3
- PHP 8.0.3
- Laravel 6.20.20
Laravel 6の環境設定
こちらの手順と同じです。
マイグレーションを修正する
マイグレーションにに項目を追加します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('postal_code');
$table->integer('pref_id');
$table->string('city');
$table->string('town');
$table->string('building')->nullable();
$table->string('phone_number');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
22行目から27行目までを追加しています。それぞれ、
- postal_code => 郵便番号
- pref_id => 都道府県ID
- city => 市区町村
- town => 町名番地等
- building => 建物等
- phone_number => 電話番号
です。buildingの項目は任意入力とし、nullable()にしています。他は必須入力です。
マイグレーションを実行します。
php artisan migrate
テンプレートに項目を追加する
Laravelが用意してくれているテンプレートに項目を追加します。下記のように追加しました。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" autocomplete="new-password">
</div>
</div>
<div class="form-group row">
<label for="postal_code" class="col-md-4 col-form-label text-md-right">{{ __('Postal Code') }}</label>
<div class="col-md-6">
<input id="postal_code" type="text" class="form-control @error('postal_code') is-invalid @enderror" name="postal_code" value="{{ old('postal_code') }}" autocomplete="postal_code" placeholder="000-0000">
@error('postal_code')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="pref" class="col-md-4 col-form-label text-md-right">{{ __('Prefectures') }}</label>
<div class="col-md-6">
<select name="pref_id" id="pref_id" class="form-control @error('pref_id') is-invalid @enderror">
<option value="">-- 選択してください --</option>
@foreach (App\User::$prefs as $key => $pref)
<option value="{{ $key }}" @if (old('pref_id') == $key) selected @endif>{{ $pref }}</option>
@endforeach
</select>
@error('pref_id')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="city" class="col-md-4 col-form-label text-md-right">{{ __('City') }}</label>
<div class="col-md-6">
<input id="city" type="text" class="form-control @error('city') is-invalid @enderror" name="city" value="{{ old('city') }}" autocomplete="city" placeholder="大阪市北区">
@error('city')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="town" class="col-md-4 col-form-label text-md-right">{{ __('Town') }}</label>
<div class="col-md-6">
<input id="town" type="text" class="form-control @error('town') is-invalid @enderror" name="town" value="{{ old('town') }}" autocomplete="town" placeholder="中之島1丁目1-1">
@error('town')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="building" class="col-md-4 col-form-label text-md-right">{{ __('Building') }}</label>
<div class="col-md-6">
<input id="building" type="text" class="form-control @error('bilding') is-invalid @enderror" name="building" value="{{ old('building') }}" autocomplete="building" placeholder="中之島○○ビル101号室">
@error('building')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="phone_number" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>
<div class="col-md-6">
<input id="phone number" type="tel" class="form-control @error('phone_number') is-invalid @enderror" name="phone_number" value="{{ old('phone_number') }}" autocomplete="phone_number" placeholder="06-0000-0000">
@error('phone_number')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
82行目から87行目、都道府県はselectボックスを使って選択できるようにしています。項目は連想配列をUserモデルに作成しています。後ほどソースコードを書きます。
項目名はヘルパ関数(__())を使って、英語名で指定しています。翻訳ファイル(resources/lang/ja.json)を使って、日本語化します。
{
"Login": "ログイン",
"Register": "会員登録",
"Name": "お名前",
"E-Mail Address": "E-mailアドレス",
"Password": "パスワード",
"Confirm Password": "パスワード再入力",
"Remember Me": "ログイン情報を記憶しておく",
"Forgot Your Password?": "パスワードを忘れた方はこちら",
"Postal Code": "郵便番号",
"Prefectures": "都道府県",
"City": "市区町村",
"Town": "町名番地等",
"Building": "建物等",
"Phone Number": "電話番号"
}
config/app.phpのロケールの値も変更しておきます。
'locale' => 'ja',
モデルの確認
まずは、モデルがどうなっているのか確認します。
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use 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',
];
}
18行目に$fillableがあるので、ここに追加した項目を書いたほうが良さそうですね。
protected $fillable = [
'name', 'email', 'password', 'postal_code', 'pref_id', 'city', 'town', 'building', 'phone_number'
];
モデルに都道府県の連想配列を書く
Userモデルに、テンプレートの都道府県のselectボックスで使用する連想配列を書きます。コントローラが肥大化するので、コントローラには書きたくない主義です。
/**
* 都道府県リスト
*
* @var array
*/
public static $prefs = [
1 => '北海道',
2 => '青森県', 3 => '岩手県', 4 => '宮城県', 5 => '秋田県', 6 => '山形県', 7 => '福島県',
8 => '茨城県', 9 => '栃木県', 10 => '群馬県', 11 => '埼玉県', 12 => '千葉県', 13 => '東京都', 14 => '神奈川県',
15 => '新潟県', 16 => '富山県', 17 => '石川県', 18 => '福井県', 19 => '山梨県', 20 => '長野県',
21 => '岐阜県', 22 => '静岡県', 23 => '愛知県', 24 => '三重県',
25 => '滋賀県', 26 => '京都府', 27 => '大阪府', 28 => '兵庫県', 29 => '奈良県', 30 => '和歌山県',
31 => '鳥取県', 32 => '島根県', 33 => '岡山県', 34 => '広島県', 35 => '山口県',
36 => '徳島県', 37 => '香川県', 38 => '愛媛県', 39 => '高知県',
40 => '福岡県', 41 => '佐賀県', 42 => '長崎県', 43 => '熊本県', 44 => '大分県', 45 => '宮崎県', 46 => '鹿児島県', 47 => '沖縄県',
];
都道府県番号は、JISの規格に則っています。
テンプレート側では、App\User::$prefsで呼び出すことができます。
本当のことをいうと、他の機能でも使うようなものはモデル以外の別のクラスに作ったほうがいいと思います。今回はテストで作っているので、とりあえずということで、Userモデルに書いています。
コントローラを修正する
ユーザー登録を司るコントローラは、app/Http/Controllers/Auth/RegisterController.phpです。こちらを修正します。編集前はこちら。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
65行目から72行目、create()というアクションメソッドがあって、こちらに項目を増やせばいいようです。また、50行目から57行目にかけて、validator()というメソッドがあるので、これでバリデーションができるようです。
バリデーションを書く
validate()メソッドの中身を下記のように追記しました。
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:50'],
'email' => ['required', 'string', 'email:rfc', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'postal_code' => ['required', 'regex:/^[0-9]{3}-[0-9]{4}$/'],
'pref_id' => ['required'],
'city' => ['required', 'max:50',],
'town' => ['required', 'max:50'],
'building' => ['max:50'],
'phone_number' => ['required', 'regex:/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{4}$/'],
],[
'name.required' => 'お名前を入力してください。',
'name.max' => 'お名前は50文字以内で入力してください。',
'email.required' => 'E-mailアドレスを入力してください。',
'email.email' => '正しいE-mailアドレスを入力してください。',
'email.max' => 'E-mailアドレスは255文字以内で入力してください。',
'email.unique' => 'そのメールアドレスは既に登録されています。',
'password.required' => 'パスワードを入力してください。',
'password.min' => 'パスワードは8文字以上で入力してください。',
'password.confirmed' => '入力されたパスワードが一致しません。',
'postal_code.required' => '郵便番号を入力してください。',
'postal_code.regex' => '郵便番号は、半角数字3桁、半角ハイフン、半角数字4桁、の形式で入力してください。',
'pref_id.required' => '都道府県を選択してください。',
'city.required' => '市区町村を入力してください。',
'city.max' => '市区町村は50文字以内で入力してください。',
'town.required' => '町名番地等を入力してください。',
'town.max' => '町名番地等は50文字以内で入力してください',
'building.required' => '建物等は50文字以内で入力してください。',
'phone_number.required' => '電話番号を入力してください。',
'phone_number.regex' => '電話番号は、半角数字と半角ハイフンで入力してください。'
]);
}
Validator::make()は、第2引数にバリデーションルールの連想配列、第3引数にカスタマイズするバリデーションエラーメッセージの連想配列を取ります。で、ソースを書いてみたのですが、非常に汚いですw
下記に書き直しました。
/**
* バリデーションエラーメッセージ
*
* @var array
*/
protected $messages = [
'name.required' => 'お名前を入力してください。',
'name.max' => 'お名前は50文字以内で入力してください。',
'email.required' => 'E-mailアドレスを入力してください。',
'email.email' => '正しいE-mailアドレスを入力してください。',
'email.max' => 'E-mailアドレスは255文字以内で入力してください。',
'email.unique' => 'そのメールアドレスは既に登録されています。',
'password.required' => 'パスワードを入力してください。',
'password.min' => 'パスワードは8文字以上で入力してください。',
'password.confirmed' => '入力されたパスワードが一致しません。',
'postal_code.required' => '郵便番号を入力してください。',
'postal_code.regex' => '郵便番号は、半角数字3桁、半角ハイフン、半角数字4桁、の形式で入力してください。',
'pref_id.required' => '都道府県を選択してください。',
'city.required' => '市区町村を入力してください。',
'city.max' => '市区町村は50文字以内で入力してください。',
'town.required' => '町名番地等を入力してください。',
'town.max' => '町名番地等は50文字以内で入力してください',
'building.required' => '建物等は50文字以内で入力してください。',
'phone_number.required' => '電話番号を入力してください。',
'phone_number.regex' => '電話番号は、半角数字と半角ハイフンで入力してください。'
];
/**
* バリデーションルール
*
* @var array
*/
protected $rules = [
'name' => ['required', 'string', 'max:50'],
'email' => ['required', 'string', 'email:rfc', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'postal_code' => ['required', 'regex:/^[0-9]{3}-[0-9]{4}$/'],
'pref_id' => ['required'],
'city' => ['required', 'max:50',],
'town' => ['required', 'max:50'],
'building' => ['max:50'],
'phone_number' => ['required', 'regex:/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{4}$/'],
];
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, $this->rules, $this->messages);
}
幾分か、きれいになったでしょうか?メソッドの中身がきれいになっているので、良しとしましょうww
ルールの”email”は、”email:rfc”と、RFCの規約に則ったバリデーションルールに変更しています。
登録処理
create()というアクションメソッドでレコードの登録を行っているようです。単純に項目を追加してみました。
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'postal_code' => $data['postal_code'],
'pref_id' => $data['pref_id'],
'city' => $data['city'],
'town' => $data['town'],
'building' => $data['building'],
'phone_number' => $data['phone_number'],
]);
}
これでうまく登録できました。ただ、項目数が増えてくると、ソースコードの行数も増えそうなので、下記のように書いてみました。
protected function create(array $data)
{
$user = new User();
$user->fill($data);
$user->password = Hash::make($data['password']);
$user->save();
return $user;
}
Userモデルに$fillableで項目を指定しているので、fill()メソッドが使えました。ただ、そのままだとパスワードが平文で登録されてしまうので、Hash::make()を使ってハッシュ化して上書きしています。
create()メソッド自体は、Userモデルクラスをreturnすると
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
こちらで指定したパスにリダイレクトするようになっているようです。
また、他のコントローラのように、
protected function create(Request $request)
{
}
引数をリクエストクラスのオブジェクトにしてみましたが、これはエラーになりました。引数は項目をキーとした連想配列($_POSTなのかな?🤔)にしないといけないようです。
ユーザー登録して表示してみる
実際にユーザー登録して、表示ができるか試してみます。
テンプレートを作成
こんな感じでテンプレートを作ってみました。
@extends('layouts.app')
@section('content')
<table class="table">
<tr>
<th class="text-right">お名前</th>
<td>{{ Auth::user()->name }}</td>
</tr>
<tr>
<th class="text-right">E-mailアドレス</th>
<td>{{ Auth::user()->email }}</td>
</tr>
<tr>
<th class="text-right">住所</th>
<td>
〒{{ Auth::user()->postal_code }}<br>
{{ App\User::$prefs[Auth::user()->pref_id] }}{{ Auth::user()->city }}<br>
{{ Auth::user()->town }}<br>
{{ Auth::user()->building }}
</td>
</tr>
<tr>
<th class="text-right">電話番号</th>
<td>{{ Auth::user()->phone_number }}</td>
</tr>
</table>
@endsection
コントローラを作成
コントローラは、とりあえずindex()アクションだけ作ります。
php artisan make:controller UserController
出来上がったコントローラを編集します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
return view('user.index');
}
}
ルーティングを追加
ルーティングを追加します。
Route::get('user', 'UserController@index')->middleware('auth');
アクセスしてみます
こんな感じで表示できました。
OKですね😃