経緯 数年前にLaravel5で構築したシステムを、Laravel11にアップデートすることになりました。
既存の仕様はそのままにフレームワークと関連ライブラリのみアップデートが必要になったのですが、 仕様の一つに「RFC違反のメールアドレス宛に送信時、エラーが発生しないこと」がありました。
「Laravel RFC違反 メール送信」といったキーワードでGoogle検索をすると実装例がいくつかヒットしますが、 Laravel9以降はSymfony Mailerが採用 されたことで、Laravel11ではそれらは使用できなくなっています。
Gmail等、一般的なメールサーバではRFC違反のメールアドレスへの送受信が禁止されていたり、 キャリア側でメールアドレス変更を推奨しているケース もあり、使用者が減ってきている印象はありますが、もし対応が必要となった場合にどのような修正が必要か調査しました。
メールアドレスの対応以外にも、既存仕様と最新版フレームワークとの調整が必要になるケースがあるかもしれませんので、対応方法のひとつとして本記事で残したいと思います。
環境 下記構成にてLaravel11が動く環境を作成済みであることが前提です。
Ubuntu22.04.2 LTS(Windows11のWSL2上に構築) PHP 8.3.4 Laravel Framework 11.0.8 Mailpit、MailCatcher 適当なMailableを作成済み(こちらの手順と同様にOrderShippedを作成しました) RFC違反のメールアドレスを使用時のエラーを確認 まず、構築直後のLaravel11でRFC違反のメールアドレス宛にメール送信時の結果を確認します。
適当なController等を用意して下記のコードを実行すると例外がスローされます。
\ Illuminate \ Support \ Facades \ Mail :: to ( '.sample@example.com' ) -> send ( new \ App \ Mail \ OrderShipped ) ;
どこで例外がスローされているか エラー内容を参考に例外のスロー元を調査すると 下記クラス に到達しました。
13行目のself::$validator->isValid()で、falseが返っているためRfcComplianceExceptionがスローされます。
public function __construct ( string $address , string $name = '' ) { if ( ! class_exists ( EmailValidator :: class ) ) { throw new LogicException ( sprintf ( 'The "%s" class cannot be used as it needs "%s". Try running "composer require egulias/email-validator".' , __CLASS__ , EmailValidator :: class ) ) ; } self :: $validator ??= new EmailValidator ( ) ; $this -> address = trim ( $address ) ; $this -> name = trim ( str_replace ( [ "\n" , "\r" ] , '' , $name ) ) ; if ( ! self :: $validator -> isValid ( $this -> address , class_exists ( MessageIDValidation :: class ) ? new MessageIDValidation ( ) : new RFCValidation ( ) ) ) { throw new RfcComplianceException ( sprintf ( 'Email "%s" does not comply with addr-spec of RFC 2822.' , $address ) ) ; } }
self::$validator->isValid()の中身は下記のようになっています。
6行目で$parser->parse()でメールアドレス形式をチェックしているようです。
public function isValid ( string $email , EmailLexer $emailLexer ) : bool { $parser = new MessageIDParser ( $emailLexer ) ; try { $result = $parser -> parse ( $email ) ; $this -> warnings = $parser -> getWarnings ( ) ; if ( $result -> isInvalid ( ) ) { $this -> error = $result ; return false ; } } catch ( \ Exception $invalid ) { $this -> error = new InvalidEmail ( new ExceptionFound ( $invalid ) , '' ) ; return false ; } return true ; }
メールアドレス自体を加工する案 例外がスローされている処理は判明しましたが、修正に着手する前に他の案が無いか考えてみます。
メールアドレスのローカルパート部分をダブルクォーテーションで囲むと、例外はスローされずメール送信は成功しました。
ただ、案件やシステムによっては、メールアドレスの加工を避けたい場合もありそうな気はします。
\ Illuminate \ Support \ Facades \ Mail :: to ( '".sample"@example.com' ) -> send ( new \ App \ Mail \ OrderShipped ) ;
※メールサーバによってはこの対応で送信できないケースもあるようです。
例外スローを回避する方法は無いか 上記以外の方法となるとAddress.phpを継承したクラスを用意して差し替えれないか挑戦しましたが クラスにfinalキーワードが付いている ので、継承や サービスコンテナの結合 は厳しそうです。
final class Address {
また、 MessageIDValidation.php にはnew MessageIDParserがハードコーディングされているのでこれを置換するのも厳しそうです。
public function isValid ( string $email , EmailLexer $emailLexer ) : bool { $parser = new MessageIDParser ( $emailLexer ) ;
何とか上記クラスを置換できないか調査や試行錯誤を繰り返しているうちに、composer.jsonでオートロードするクラスを置換する方法に行き着きました。
MessageIDValidation.phpの処理でメールアドレス形式のチェックが通らないため例外スローに繋がっているため、これを置換すれば良いと考えcomposer.jsonの一部分を下記のように編集しました。
"autoload" : { "psr-4" : { "App\\" : "app/" , "Database\\Factories\\" : "database/factories/" , "Database\\Seeders\\" : "database/seeders/" } , "exclude-from-classmap" : [ "vendor/egulias/email-validator/src/Validation/MessageIDValidation.php" ] , "files" : [ "vendor-overrides/egulias/email-validator/src/Validation/MessageIDValidation.php" ] } ,
filesに指定したファイルは下記を用意しました。
例外スローの原因となっているisValid()で常にtrueを返しています。
※今回は簡易的に対応するため下記ソースとなりましたが、実際に使用する時は不要な処理だけ削除等を検討した方が良さそうです。
namespace Egulias \ EmailValidator \ Validation ; use Egulias \ EmailValidator \ EmailLexer ; use Egulias \ EmailValidator \ Result \ InvalidEmail ; use Egulias \ EmailValidator \ Warning \ Warning ; class MessageIDValidation implements EmailValidation { private $warnings = [ ] ; private $error ; public function isValid ( string $email , EmailLexer $emailLexer ) : bool { return true ; } public function getWarnings ( ) : array { return $this -> warnings ; } public function getError ( ) : ? InvalidEmail { return $this -> error ; } }
下記を実行すればオートロードするクラスを変更できます。
$ composer dump-autoload
再度RFC違反のメールアドレス宛にメールを送信する 再度メール送信実行してみますと、今度はエラー画面が表示されずに終了します。
\ Illuminate \ Support \ Facades \ Mail :: to ( '.sample@example.com' ) -> send ( new \ App \ Mail \ OrderShipped ) ;
最後に RFC違反のメールアドレス宛にメール送信時、例外スローを抑制する方法でした。
着手前はクラスの継承や サービスコンテナの結合 で対応できると想定していたのですが、メール送信の処理を追って行くうちにそれが無理だと思い、最終的にcomposer.jsonで、クラスを置換することになりました。
今回はメールアドレスを例に説明させていただきましたが「Laravel11が想定していない処理を実装する方法」としても参考にしていただければと思います。
最後までお読みいただき、ありがとうございました。
記事を読んで興味を持った方はぜひコチラから↓