0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【備忘録】Laravel + Vueで作った認証フォームにバリデーション機能を加える

Last updated at Posted at 2021-04-09

バージョン等

PHP7.3
Laravel7.30
Vue2.6.12
ローカル環境(ログイン機能まで実装したもの)

バリデーションルール

新規登録フォーム

カラム名 ルール
name 必須
2文字以上20文字以下
email 必須
メールアドレスの形式
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>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?