バージョン等
PHP7.3
Laravel7.30
Vue2.6.12
ローカル環境(ログイン機能まで実装したもの)
バリデーションルール
新規登録フォーム
| カラム名 | ルール |
|---|---|
| name | 必須 2文字以上20文字以下 |
| 必須 メールアドレスの形式 |
|
| login_id | 必須 使える文字は半角英数字、ハイフン「-」、アンダーバー「_」、ピリオド「.」 8文字以上20文字以下 |
| password | 必須 大文字、小文字、数字をそれぞれ1種類以上含める 8文字以上20文字以下 |
| password_confirmation | 必須 passwordと一致が必要 |
ログインフォーム
| カラム名 | ルール |
|---|---|
| login_id | 必須 |
| password | 必須 |
Laravel側
app/Http/Controllers/Auth/RegisterController.php
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'min:2', 'max:20'],
'login_id' => ['required', 'regex:/^[\w\.\-]+$/i', 'min:8', 'max:20', Rule::unique('users', 'login_id')->whereNull('deleted_at')],
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')->whereNull('deleted_at')],
'password' => ['required', 'string', 'regex:/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]+$/', 'min:8', 'max:20', 'confirmed'],
]);
}
日本語対応
config/app.php
'locale' => 'ja',
resources/langディレクトリ以下にjaディレクトリを作り、バリデーションメッセージに関するファイルを置いておく。
今回はこちらのページからダウンロードし、zip解凍後jaフォルダごとまるっと移す
resources/lang/ja/validation.php
// 追加
'attributes' => [
'login_id' => 'ログインID',
'password' => 'パスワード',
'name' => 'お名前',
'email' => 'メールアドレス',
],
resources/lang/ja/auth.php
'failed' => 'ログインIDまたはパスワードが一致しません。',
Vue側
~/workspace/myapp/laravel
$ touch resources/ts/types/Form.ts
$ mkdir resources/ts/mixins
$ touch resources/ts/mixins/formValidator.ts
resources/ts/types/Form.ts
export type ValidateRule = (inputData: any) => boolean | string
export type Base = {
name: string
type: string
rules: ValidateRule[]
errorMessages: (string | boolean)[]
placeholder?: string
[k: string]: any
}
export type Form = {
[k: string]: Base
}
resources/ts/mixins/formValidator.ts
import { Mixin, Component } from 'vue-mixin-decorator'
import { Vue, Watch } from 'vue-property-decorator'
import { Form, Base } from '@/types/Form'
import { ValidateRule } from '@/types/Form'
import { ErrorMessages } from '@/types/Error'
@Mixin
export default class FormValidator extends Vue {
submitData: Form = {}
getValidatedMessage(rules: ValidateRule[], formVal: string): (string | boolean)[] {
return rules.map((rule: ValidateRule) => rule(formVal))
.filter((res: string | boolean) => typeof res === 'string')
}
validation(formItem: Base): void {
formItem.errorMessages = this.getValidatedMessage(formItem.rules, formItem.value)
}
get disabled(): boolean {
return this.errorCount > 0
}
get errorCount(): number {
const count: number[] = Object.keys(this.submitData).map((key: string) =>
this.getValidatedMessage(this.submitData[key].rules, this.submitData[key].value).length
)
return count.reduce((prev, current, i, count) => prev + current)
}
get errMessages(): ErrorMessages {
return this.$store.getters['error/messages']
}
@Watch('errMessages', { immediate: true })
fetchMessage(val: ErrorMessages): void {
if (val !== null) {
Object.keys(val).forEach((key: string): void => {
this.submitData[key].errorMessages = val[key]
})
}
}
}
resources/ts/pages/Register.vue
<template>
<div class="container">
<h1>会員登録</h1>
<form @submit.prevent="register">
<div v-for="(formItem, index) in submitData" :key="index">
<p v-if="formItem.errorMessages">{{ formItem.errorMessages[0] }}</p>
<input
v-if="formItem.type==='text'"
type="text"
v-model="formItem.value"
:placeholder="formItem.placeholder"
@keyup="validation(formItem)"
required
>
<input
v-else-if="formItem.type==='email'"
type="email"
v-model="formItem.value"
:placeholder="formItem.placeholder"
@keyup="validation(formItem)"
required
>
<input
v-else-if="formItem.type==='password'"
type="password"
v-model="formItem.value"
@keyup="validation(formItem)"
:placeholder="formItem.placeholder"
required
>
</div>
<div>
<button :disabled="disabled">会員登録</button>
</div>
</form>
</div>
</template>
<script lang="ts">
import { Component, Mixins} from 'vue-property-decorator'
import { Form } from '@/types/Form'
import FormValidator from '@/mixins/formValidator'
@Component
export default class Register extends Mixins(FormValidator) {
submitData: Form = {
name: {
name: 'お名前',
type: 'text',
value: '',
rules: [
(val: string) => !!val || '必須項目です',
(val: string) => (val.length >= 2 && val.length <= 20) || '2文字以上20文字以下で設定してください',
],
errorMessages: [],
placeholder: 'your nickname',
},
email: {
name: 'メールアドレス',
type: 'email',
value: '',
rules: [
(val: string) => !!val || '必須項目です',
(val: string) => !!val.match(/^\w+[\w\.]*\w+@\w+[\w\.]*\.\w+[\w\.]*[A-Za-z]+$/i) || 'メールアドレスの形式が正しくありません'
],
errorMessages: [],
placeholder: 'sample@example.com',
},
login_id: {
name: 'ログインID',
type: 'text',
value: '',
rules: [
(val: string) => !!val || '必須項目です',
(val: string) => !!val.match(/^[\w\.\-]+$/g) || '文字は半角英数字、「.」、「_」、「-」のみ指定できます',
(val: string) => val.length >= 8 && val.length <= 20 || '8文字以上20文字以下で入力してください'
],
errorMessages: [],
placeholder: 'login ID',
},
password: {
name: 'パスワード',
type: 'password',
value: '',
rules: [
(val: string) => !!val || '必須項目です',
(val: string) => !!val.match(/^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]+$/) || '半角小文字、半角大文字、数字をそれぞれ1種類以上含めてください',
(val: string) => (val.length >= 8 && val.length <= 20) || '8文字以上20文字以下で入力してください',
],
errorMessages: [],
placeholder: 'at leeast 8 chars.',
},
password_confirmation: {
name: 'パスワード(確認用)',
type: 'password',
value: '',
rules: [
(val: string) => !!val || '必須項目です',
(val: string) => this.submitData.password.value === val || 'パスワードが一致しません'
],
errorMessages: [],
placeholder: 'confirm',
},
}
async register(): Promise<void> {
if (this.disabled) {
Object.keys(this.submitData).forEach((key: string): void => {
this.validation(this.submitData[key])
})
} else {
const formData = new FormData()
formData.append('name', this.submitData.name.value)
formData.append('email', this.submitData.email.value)
formData.append('login_id', this.submitData.login_id.value)
formData.append('password', this.submitData.password.value)
formData.append('password_confirmation', this.submitData.password_confirmation.value)
await this.$store.dispatch('auth/register', formData)
}
}
}
</script>
resources/ts/pages/Login.vue
<template>
<div>
<h1>ログイン</h1>
<form @submit.prevent="login">
<div v-for="(formItem, index) in submitData" :key="index">
<p v-if="formItem.errorMessages">{{ formItem.errorMessages[0] }}</p>
<input
v-if="formItem.type==='text'"
type="text"
v-model="formItem.value"
:placeholder="formItem.placeholder"
@keyup="validation(formItem)"
required
>
<input
v-else-if="formItem.type==='password'"
type="password"
v-model="formItem.value"
@keyup="validation(formItem)"
:placeholder="formItem.placeholder"
required
>
</div>
<div>
<button :disabled="disabled">ログイン</button>
</div>
</form>
</div>
</template>
<script lang="ts">
import { Component, Mixins } from 'vue-property-decorator'
import { Form } from '@/types/Form'
import FormValidator from '@/mixins/formValidator'
@Component
export default class Login extends Mixins(FormValidator) {
submitData: Form = {
login_id: {
name: 'login_id',
type: 'text',
value: '',
rules: [(val: string) => !!val || '必須項目です'],
errorMessages: [],
placeholder: 'login_id',
},
password: {
name: 'password',
type: 'password',
value: '',
rules: [(val: string) => !!val || '必須項目です'],
errorMessages: [],
placeholder: 'at least 8 chars.',
},
}
async login(): Promise<void> {
if (this.disabled) {
Object.keys(this.submitData).forEach((key: string): void => {
this.validation(this.submitData[key])
})
} else {
const formData = new FormData()
formData.append('login_id', this.submitData.login_id.value)
formData.append('password', this.submitData.password.value)
await this.$store.dispatch('auth/login', formData)
}
}
}
</script>