Laravel RFC違反のメールアドレスにメール送信できるようにする

## ストーリー

[Laravel RFC違反しているメールアドレスをサクッと探すtips](https://qiita.com/ucan-lab/items/52a66a05a06844b93ee7)より。

RFC違反しているメールアドレスを見つけたところで、送信できるようにして欲しいという無茶苦茶な要望があります。

そんな無茶振りに対応する方法をご紹介します。

## 環境

- PHP 7.2
- Laravel 5.7

## RFC違反のメールアドレスの例

- `example.-.@example.com`
  - `@`の前に`.`を置いてはならない
- `.example@example.com`
  - `.`から開始してはならない
- `ex..ample@example.com`
  - `.`が連続してはいけない

## エラー

```shell-session
$ php artisan tinker
```

このRFC違反メールアドレスにLaravelからメールを送信しようとすると`Swift_RfcComplianceException`という例外が発生します。

```php
Mail::raw('test mail',function($message){$message->to('example.-.@example.com')->subject('test');});

Swift_RfcComplianceException with message 'Address in mailbox given [test.@example.com] does not comply with RFC 2822, 3.6.2.'
```

## RFC 2822

[電子メール](https://www.wdic.org/w/WDIC/電子メール)に関するきまりが書かれた[RFC](https://www.wdic.org/w/WDIC/RFC)。[RFC 822](https://www.wdic.org/w/WDIC/RFC%20822)を破棄し、新標準となった。

- [RFC 2822](https://www.wdic.org/w/WDIC/RFC%202822)
- [メールのアドレスヘッダの仕様](https://qiita.com/hatanakaakihiro/items/30f15696c864699a2dba)
- [RFC 2822 Internet Message Format](http://www.t-net.ne.jp/~cyfis/rfc/mail/rfc2822_ja-1.html)

## 対応策

`Swiftmailer`のコンテナに独自のバリデータを読み込ませる事で対応します。

### app/Validation/AllowNonRfcComplaintEmailValidator.php

```php
<?php

namespace App\Validation;

use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\EmailValidation;

class AllowNonRfcComplaintEmailValidator extends EmailValidator
{
    /**
     * 最低限、@が含まれていて、ドメインとアカウント名が含まれているメールアドレスは許可します
     * @param string          $email
     * @param EmailValidation $emailValidation
     * @return bool
     */
    public function isValid($email, EmailValidation $emailValidation): bool
    {
        // warningsとerrorのプロパティを埋める
        parent::isValid($email, $emailValidation);

        if (substr_count($email, '@') !== 1) {
            return false;
        }

        list($account, $domain) = explode('@', $email);

        if (strlen($account) > 0 && strlen($domain) > 0) {
            return true;
        }

        return false;
    }
}
```

[`parent::isValid($email, $emailValidation)`](https://github.com/egulias/EmailValidator/blob/2.1.11/EmailValidator/EmailValidator.php#L30-L42) 戻り値は使用してないですが、
親関数ので実行している `warnings`, `errors` プロパティを埋める処理を呼び出してます。

### app/Providers/AllowNonRfcComplaintEmailPatchServiceProvider.php

```php
<?php

namespace App\Providers;

use App\Validation\AllowNonRfcComplaintEmailValidator;
use Illuminate\Support\ServiceProvider;
use Swift;
use Swift_DependencyContainer;

class AllowNonRfcComplaintEmailPatchServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot(): void
    {
        Swift::init(
            function () {
                Swift_DependencyContainer::getInstance()
                    ->register('email.validator')
                    ->asSharedInstanceOf(AllowNonRfcComplaintEmailValidator::class);
            }
        );
    }
}
```

### config/app.php

```php
return [
    'providers' => [
        // ...
        App\Providers\AllowNonRfcComplaintEmailPatchServiceProvider::class,
    ],
];
```

## 動作確認

```shell-session
$ php artisan tinker
```

```php
Mail::raw('test mail',function($message){$message->to('example.-.@example.com')->subject('test');});

null
```

成功したら`null`が返ってきます。

## 参考

- [tecpresso/laravel-allow-non-rfc-compliant-email-patch](https://github.com/tecpresso/laravel-allow-non-rfc-compliant-email-patch)
- [Laravel 5.5+ でRFC違反のメールを送信できるようにするパッチを作成しました](https://qiita.com/mikkame/items/0ff8cc888e1233d03c06)