Laravel Sanctum,FortifyでSPAのログイン機能を実装する(バリデーション)

# はじめに
前回実装したログイン機能に、Vuetifyでバリデーションを実装します。
公式ドキュメントを参考に進めていきます。

https://vuetifyjs.com/ja/components/text-fields

https://vuetifyjs.com/ja/components/forms

前回：https://qiita.com/HuntingRathalos/items/b401ee865c40e94cef46

# 環境
- Vue：2.6.14
- Vuetify：2.6.1
# 目次
[1.ベースとなるコード](#1-ベースとなるコード)
[2.バリデーション実装](#2-バリデーション実装)
　　[2-1.各テキストフィールドに制約をつける](#2-1-各テキストフィールドに制約をつける)
　　[2-2.フォーム全体をバリデーションチェック](#2-2-フォーム全体をバリデーションチェック)
[3.参考記事](#3-参考記事)

# 1. ベースとなるコード
下記のコードにバリデーションをかけていきます。

<details><summary><b>EmailInput.vue</b></summary><div>

```vue:EmailInput.vue
<template>
  <v-text-field
    v-model="setEmail"
    label="メールアドレスを入力"
    placeholder="your@email.com"
    type="email"
    outlined
    dense
    rounded
  />
</template>
<script>
export default {
  props: {
    email: {
      type: String,
      default: ''
    }
  },
  computed: {
    setEmail: {
      get() {
        return this.email
      },
      set(newVal) {
        return this.$emit('update:email', newVal)
      }
    }
  }
}
</script>
```

</div></details>

<details><summary><b>PasswordInput.vue</b></summary><div>

```vue:PasswordInput.vue
<template>
  <v-text-field
    v-model="setPassword"
    type="password"
    label="パスワードを入力"
    placeholder="8文字以上"
    outlined
    rounded
    dense
  />
</template>
<script>
export default {
  props: {
    password: {
      type: String,
      default: ''
    }
  },
  computed: {
    setPassword: {
      get() {
        return this.password
      },
      set(newVal) {
        return this.$emit('update:password', newVal)
      }
    }
  }
}
</script>
```

</div></details>

<details><summary><b>pages/login.vue</b></summary><div>

```vue:pages/login.vue
<template>
  <v-card max-width="500" class="mx-auto mt-5 full-width" flat outlined>
    <v-card-title class="text-center pa-8">
      <h1 class="text-h5 font-weight-bold full-width">ログイン情報入力</h1>
    </v-card-title>
    <v-divider> </v-divider>
    <div class="px-6 py-8">
      <div style="max-width: 336px" class="mx-auto">
        <v-card-text>
          <v-form @submit.prevent="doLogin">
            <email-input :email.sync="form.email" />
            <password-input :password.sync="form.password" />
            <v-card-actions>
              <v-row justify="end" class="pb-8 pt-4">
                <base-button :color="btnColor">ログイン</base-button>
              </v-row>
            </v-card-actions>
          </v-form>
        </v-card-text>
      </div>
    </div>
  </v-card>
</template>
<script>
import BaseButton from '../../atoms/buttons/BaseButton.vue'
import EmailInput from '../../atoms/inputs/EmailInput.vue'
import PasswordInput from '../../atoms/inputs/PasswordInput.vue'
export default {
  components: { EmailInput, PasswordInput, BaseButton },
  data() {
    return {
      btnColor: 'indigo accent-2',
      form: {
        email: '',
        password: ''
      }
    }
  },
  methods: {
    async doLogin() {
      try {
        await this.$auth.loginWith("laravelSanctum", {
          data: this.form,
        })
		this.$router.push('/')
      } catch (e) {}
    },
  },
}
</script>
<style scoped>
.full-width {
  width: 100%;
}
</style>
```

</div></details>

# 2. バリデーション実装

### 2-1. 各テキストフィールドに制約をつける
Vuetifyの`v-text-field`にバリデーションを設定するには、バリデーション用関数を`rules`属性に配列で渡します。

今回は、**必須入力**、**文字数制限**、**メール形式入力**のチェックをする関数を用意しました。

それぞれ下記のようなコードになります。
```js:必須入力
(value) => !!value || '必須項目なので値を入力してください。'
```
```js:文字数制限
(value) => value.length <= 255 || '文字数をオーバーしています。'
```
```js:メール形式入力
(value) => {
          const pattern =
            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
          return pattern.test(value) || '正しい形式で入力してください。'
        }
```
入力された文字列がチェックを通れば`true`を返し、通らなければエラー文字列を返します。

これらの関数を`rules`属性に渡すことで、検証することができます。

修正後、最終的なコードは下記のようになりました。

<details><summary><b>EmailInput.vue（修正後）</b></summary><div>

```vue:EmailInput.vue
<template>
  <v-text-field
    v-model="setEmail"
    :rules="[rules.required, rules.email, rules.maxCount255]" //追加
    label="メールアドレスを入力"
    placeholder="your@email.com"
    type="email"
    outlined
    dense
    rounded
  />
</template>
<script>
export default {
  props: {
    email: {
      type: String,
      default: ''
    }
  },
	//ここから追加
  data() {
    return {
      rules: {
        required: (value) => !!value || '必須項目なので値を入力してください。',
        maxCount255: (value) =>
          value.length <= 255 || '文字数をオーバーしています。',
        email: (value) => {
          const pattern =
            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
          return pattern.test(value) || '正しい形式で入力してください。'
        }
      }
    }
  },
	//ここまで
  computed: {
    setEmail: {
      get() {
        return this.email
      },
      set(newVal) {
        return this.$emit('update:email', newVal)
      }
    }
  }
}
</script>
```

</div></details>

<details><summary><b>PasswordInput.vue（修正後）</b></summary><div>

```vue:PasswordInput.vue
<template>
  <v-text-field
    v-model="setPassword"
    :rules="[rules.required, rules.minCount8]" //追加
    type="password"
    label="パスワードを入力"
    placeholder="8文字以上"
    outlined
    rounded
    dense
  />
</template>
<script>
export default {
  props: {
    password: {
      type: String,
      default: ''
    }
  },
	//ここから追加
  data() {
    return {
      rules: {
        required: (value) => !!value || '必須項目なので値を入力してください。',
        minCount8: (value) =>
          value.length >= 8 || 'パスワードは8文字以上で入力してください。'
      }
    }
  },
	//ここまで
  computed: {
    setPassword: {
      get() {
        return this.password
      },
      set(newVal) {
        return this.$emit('update:password', newVal)
      }
    }
  }
}
</script>
```

</div></details>



### 2-2. フォーム全体をバリデーションチェック
現状で、各テキストフィールドに制約をつけることはできたが、不正値の入力があっても送信されてしまうと言う問題点があります。

そのために、コードを修正していきます。

まず、下記のように`v-form`に`ref`属性を付与してVue側からアクセスできるようにします。
```vue
<v-form　ref="login_form">
//略
</v-form>
```
`v-form`に`ref`を設定することで、`validate()`にアクセスできるようになっています。
`validate()`は、すべての要素がバリデーションを通過しているかを確認してくれるので、バリデーションをかけたいタイミングで呼びます。

修正後、最終的なコードは下記のようになりました。

<details><summary><b>pages/login.vue</b></summary><div>

```vue:pages/login.vue
<template>
  <v-card max-width="500" class="mx-auto mt-5 full-width" flat outlined>
    <v-card-title class="text-center pa-8">
      <h1 class="text-h5 font-weight-bold full-width">ログイン情報入力</h1>
    </v-card-title>
    <v-divider> </v-divider>
    <div class="px-6 py-8">
      <div style="max-width: 336px" class="mx-auto">
        <v-card-text>
          <v-form　ref="login_form" @submit.prevent="doLogin"> //ref属性で名前をつける
            <email-input :email.sync="form.email" />
            <password-input :password.sync="form.password" />
            <v-card-actions>
              <v-row justify="end" class="pb-8 pt-4">
                <base-button :color="btnColor">ログイン</base-button>
              </v-row>
            </v-card-actions>
          </v-form>
        </v-card-text>
      </div>
    </div>
  </v-card>
</template>
<script>
import BaseButton from '../../atoms/buttons/BaseButton.vue'
import EmailInput from '../../atoms/inputs/EmailInput.vue'
import PasswordInput from '../../atoms/inputs/PasswordInput.vue'
export default {
  components: { EmailInput, PasswordInput, BaseButton },
  data() {
    return {
      btnColor: 'indigo accent-2',
      form: {
        email: '',
        password: ''
      }
    }
  },
  methods: {
    async doLogin() {
      if (this.$refs.login_form.validate()) { //追加
        try {
          await this.$auth.loginWith('laravelSanctum', {
            data: this.form
          })
          this.$router.push('/')
        } catch {}
      }　//追加
    }
  }
}
</script>
<style scoped>
.full-width {
  width: 100%;
}
</style>
```

</div></details>

以上で実装完了となります。

# 3. 参考記事

https://zenn.dev/gz/articles/4cfa9f0ed0a91b

https://masa-enjoy.com/vuetify-form-validation