Laravelのemailバリデーションルールは何をチェックしているのかというと

## 本文

Laravel のバリデーションには `email` というルールが用意されており、そのものずばりフィールドがメールアドレスとして正しいことをバリデートします。

実体としては`filter_var()`で`FILTER_VALIDATE_EMAIL`のチェックをしている[^hoge]ため、RFC822違反のメールアドレスなどはチェックできません。

対応としては、いまどきそんなメールアドレスは無いと割り切る[^yes]か、またはカスタムバリデーションを書くのかな:whale2:

~~おわりです。~~

## カスタムバリデーション

@mpyw さんの [PHPで各種バリデーション - Qiita](http://qiita.com/mpyw/items/346f1789ad0e1b969ebc#2015811-%E8%BF%BD%E8%A8%98-1)をほとんどそのままいただきました。

```php
        Validator::extend('simple_email', function ($attribute, $value, $parameters, $validator) {
          /*
          * ドメインの存在確認する？
          */
          $check_dns = empty($parameters[0]) ? 0 : 1;

          switch (true) {
              /*
              * PHP7.1.0 よりも前では FILTER_FLAG_EMAIL_UNICODE が無いため
              * filter_var($email, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE)
              * と書けないのです。
              */
              case false === filter_var($value, FILTER_VALIDATE_EMAIL):
              case !preg_match('/@(?!\[)(.+)\z/', $value, $m):
                  return false;
              case !$check_dns:
              case checkdnsrr($m[1], 'MX'):
              case checkdnsrr($m[1], 'A'):
              case checkdnsrr($m[1], 'AAAA'):
                  return true;
              default:
                  return false;
          }
        }, 'メールアドレスの形式が不正です。');
```

```php:つかいかた
        Validator::make($request->all(), [
            'mail1' => 'simple_email', // DNSレコードのチェックはしない
            'mail2' => 'simple_email:1', // DNSレコードのチェックもする
        ]);

```

こんどこそおわりです。


## 調査メモ
#### Laravel5.4

```bash:Laravelをインストールしてバリデーションのソースを参照しよう
$ composer global require "laravel/installer"
$ laravel new blog

$ php blog/artisan --version
Laravel Framework 5.4.0

$ less blog/vendor/laravel/framework/src/Illuminate/Validation/Concerns/ValidatesAttributes.php
```

```php:ValidatesAttributes.phpの該当部分
    /**
     * Validate that an attribute is a valid e-mail address.
     *
     * @param  string  $attribute
     * @param  mixed   $value
     * @return bool
     */
    protected function validateEmail($attribute, $value)
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }
```

#### PHP7.1.1

```bash:ダウンロードしたPHPのソースを参照しよう
$ tar xzvf php-7.1.1.tar.gz

$ less php-7.1.1/ext/filter/logical_filters.c
```

```c:logical_filters.cの該当部分
void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
{
        /*
         * The regex below is based on a regex by Michael Rushton.
         * However, it is not identical.  I changed it to only consider routeable
         * addresses as valid.  Michael's regex considers a@b a valid address
         * which conflicts with section 2.3.5 of RFC 5321 which states that:
         *
         *   Only resolvable, fully-qualified domain names (FQDNs) are permitted
         *   when domain names are used in SMTP.  In other words, names that can
         *   be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
         *   in Section 5) are permitted, as are CNAME RRs whose targets can be
         *   resolved, in turn, to MX or address RRs.  Local nicknames or
         *   unqualified names MUST NOT be used.
         *
         * This regex does not handle comments and folding whitespace.  While
         * this is technically valid in an email address, these parts aren't
         * actually part of the address itself.
         *
         * Michael's regex carries this copyright:
         *
         * Copyright © Michael Rushton 2009-10
         * http://squiloople.com/
         * Feel free to use and redistribute this code. But please keep this copyright notice.
         *
         */
        pcre       *re = NULL;
        pcre_extra *pcre_extra = NULL;
        int preg_options = 0;
        int         ovector[150]; /* Needs to be a multiple of 3 */
        int         matches;
        zend_string *sregexp;
        const char regexp0[] = "/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E\\pL\\pN]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F\\pL\\pN]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E\\pL\\pN]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F\\pL\\pN]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iDu";
        const char regexp1[] = "/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD";
        const char *regexp;
        size_t regexp_len;

        if (flags & FILTER_FLAG_EMAIL_UNICODE) {
                regexp = regexp0;
                regexp_len = sizeof(regexp0) - 1;
        } else {
                regexp = regexp1;
                regexp_len = sizeof(regexp1) - 1;
        }

        /* The maximum length of an e-mail address is 320 octets, per RFC 2821. */
        if (Z_STRLEN_P(value) > 320) {
                RETURN_VALIDATION_FAILED
        }

        sregexp = zend_string_init(regexp, regexp_len, 0);
        re = pcre_get_compiled_regex(sregexp, &pcre_extra, &preg_options);
        if (!re) {
                zend_string_release(sregexp);
                RETURN_VALIDATION_FAILED
        }
        zend_string_release(sregexp);
        matches = pcre_exec(re, NULL, Z_STRVAL_P(value), (int)Z_STRLEN_P(value), 0, 0, ovector, 3);

        /* 0 means that the vector is too small to hold all the captured substring offsets */
        if (matches < 0) {
                RETURN_VALIDATION_FAILED
        }

}
/* }}} */
```

IPv6やPunycode対応のためか、自分が知ってた頃よりさらにチェックロジックが複雑になっている…

## 参考と注釈

- [Laravel 5.4 Documentation](https://laravel.com/docs/5.4)
	- [Laravel 5.4 Documentation - Validation](https://laravel.com/docs/5.4/validation)

> 'email'
> The field under validation must be formatted as an e-mail address.

- [PHP マニュアル > 関数リファレンス > 変数・データ型関連 > Filter > Filter 関数](http://php.net/manual/ja/function.filter-var.php)
	- [PHP マニュアル > 関数リファレンス > 変数・データ型関連 > Filter > フィルタの型](http://jp2.php.net/manual/ja/filter.filters.validate.php)

> 'FILTER_VALIDATE_EMAIL'
> 値が妥当な e-mail アドレスであるかどうかを検証します。
> この検証は、e-mail アドレスが RFC 822 に沿った形式であるかどうかを確かめます。 ただし、コメントおよび空白の折り返し (whitespace folding) には対応していません。

- [RFC822: Standard for ARPA Internet Text Messages](https://www.w3.org/Protocols/rfc822/)
- [Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。 - Qiita](http://qiita.com/fagai/items/9904409d3703ef6f79a2)
- [filter_var関数でメールアドレスをバリデーションする - Qiita](http://qiita.com/dayone80/items/b823aec07ed71bf26c7e)
- [PHPで各種バリデーション - Qiita](http://qiita.com/mpyw/items/346f1789ad0e1b969ebc)

- [「PHP使いはもう正規表現をblogに書くな」と言わせないでくれ - 404 Blog Not Found](http://blog.livedoor.jp/dankogai/archives/51189905.html)

[^yes]: あきらめ
[^hoge]: すくなくとも、わたくしがさわりはじめたLaravel４からこうでした。