とりあえず「拡張アドレスの拡張部分を除去」が命題になるのですが…もうちょっと正しく書くとおそらくは違って、さらっとぶっちゃけると「DBでprimary keyだったりUNIQUE制約かかってたりするのにちゃんと引っかかる」状況を作り出したい、ってのが、おそらく本筋。
で…ここから少し面倒なお話し。
とりあえず標的に上がってるのは「+(プラス)」記号で、これが割と拡張アドレスとしてつかわれ「やすい」。
ただ、拡張アドレスはqmailだと「−(ハイフン)」だし、Postfixだと「設定によっては+も−も使える(Postfix 2.11以降)」。一方で「普通にlocal-partの文字として−(ハイフン)が使える」所もあるので、色々と悩ましい。
それ以外にも「quoted string(ダブルクォートで囲った文字列)をlocal-partにする」ってやり方もあってこれも割とあちこちで使えるし(斜め調査の限りでは、AUのスマホの「quoted stringアドレスへの送信」のみNGで、後は送受信とも割といける)。
あと、これはgmail以外では見ていないんだけど「Gmail ではユーザー名のピリオドを文字として認識しません( https://support.google.com/mail/answer/10313?hl=ja )」という謎仕様もあるので、ドット位置変えると割と色々いける(ドットの連続はどうもGoogle側で(も?)弾いてるぽい。「Final-Recipient: rfc822; "h..oge"@gmail.com」ってエラーがきた)。
この辺を考えると、細かくいくと「ドメイン毎に処理を変える」必要が出てきそうではあるんだけど。
大まかな「local-partからいらんものを引っぺがす!」ってのを前提に
・前後のダブルクォートは、あったら引っぺがす
・domain-partの文字は(小文字側に)揃える。local-partは「区別したりしなかったり」だと思うので、一旦「小文字に揃える」をデフォに。「小文字に揃えない(大文字と小文字を区別するMTA(RFC822準拠、RFC5321非準拠?)」の可能性が0ではないので、ギミックだけ作って運用対処としておく。
・+以降は「拡張アドレスの可能性が高い」ので、+以降を引っぺがす(後で「引っぺがさないドメインリスト」とか作って運用で足し込む)
・−以降は「拡張アドレスの可能性がある」ので、−以降を引っぺがす準備をする(「引っぺがすドメインリスト」とか作って運用で足し込む。初期は空でいいのかも)
・Googleアカウントについては「ドットを消す」:いるかなぁ? とも思うんだけど、可能なのでせっかくだしやっとく。これも「引っぺがすリスト」作って、後で追加対応にしておくといいのかなぁ?
こんな感じ。
ちなみに前提として「アドレスは到達する」事を期待しているので、ドメイン周りは未着手。
ドメイン周りに手を出したい場合、最終的には「DNS引くよねぇ」って話になるので、それはまた「やるならやって」的なところw
文章書きながらで実装3時間ちょい…うち1時間弱が「調査+書き物」、1時間が「コーディング」、1時間がテスト。
テストの大半は「テストデータの用意ミス」という切ないっぷり(苦笑
とりあえず「さほどコストかからずに書けるよ」って程度なので、一旦「プロトタイプ的なブツだけどこんなもんぢゃね?」程度のものを。
っつわけで、コード(+下の方に簡単なテスト付き)
<?php // 拡張アドレスっぽいもの除去クラス class extended_address_removal { // 本当は「外部ファイルから読み込み」とかで適切に読み込んでくだせぇ(苦笑 public function __construct() { // $this->not_lower_list([ 'not-lower-example.com', ]); // $this->plus_unremove_list([ 'plus-unremove-example.com', 'hyphen-only-remove-example.com', // XXX ここ「二重登録」だなぁ…直感的じゃない…後で変えたほうがいいかも:これは「ハイフンのみ除去対象、+は生かす」設定用 ]); // $this->hyphen_remove_list([ 'hyphen-only-remove-example.com', // XXX ここ「二重登録」だなぁ…直感的じゃない…後で変えたほうがいいかも:これは「ハイフンのみ除去対象、+は生かす」設定用 'hyphen-remove-example.com', // こっちだと「ハイフンもプラスも除去する」になる ]); // $this->dot_remove_list([ 'gmail.example.com', ]); } // 設定する系メソッド // 検索しやすいようにarray_flipしてるのでほんのり注意 public function not_lower_list($awk) { $this->not_lower_list_ = array_flip($awk); } public function plus_unremove_list($awk) { $this->plus_unremove_list_ = array_flip($awk); } public function hyphen_remove_list($awk) { $this->hyphen_remove_list_ = array_flip($awk); } public function dot_remove_list($awk) { $this->dot_remove_list_ = array_flip($awk); } // 除去本体 public function remove($email) { // local-partとdomain-partに切り分ける $awk = explode('@', $email); // 万が一「@無しの文字列」が入ってきた時用 // XXX このチェックいるか? if (1 === count($awk)) { return ''; } // $domain = array_pop($awk); $local = implode('@', $awk); //var_dump($local); //var_dump($domain); // domain-partの文字は(小文字側に)揃える。local-partは「区別したりしなかったり」だと思うので、一旦「小文字に揃える」をデフォに。「小文字に揃えない(大文字と小文字を区別するMTA(RFC822準拠、RFC5321非準拠?)」の可能性が0ではないので、ギミックだけ作って運用対処としておく。 if (false === isset($this->not_lower_list_[$domain])) { $local = strtolower($local); } // 前後のダブルクォートは、あったら引っぺがす // XXX 不完全なquoted stringは一旦弾こう $flg = ('"' === $local[0]); // 先頭の"チェック $flg2 = ('"' === $local[strlen($local) - 1]); // 最後の"チェック // 「どっちかだけtrue」は不完全なquoted stringなのでエラー if ($flg xor $flg2) { return ''; } // quoted stringなら除去処理 if (true === $flg) { $local = substr($local, 1, -1); } //var_dump($local); //var_dump($domain); // ・+以降は「拡張アドレスの可能性が高い」ので、+以降を引っぺがす(後で「引っぺがさないドメインリスト」とか作って運用で足し込む) if (false === isset($this->plus_unremove_list_[$domain])) { $awk = explode('+', $local); $local = $awk[0]; } // ・−以降は「拡張アドレスの可能性がある」ので、−以降を引っぺがす準備をする(「引っぺがすドメインリスト」とか作って運用で足し込む。初期は空でいいのかも) if (true === isset($this->hyphen_remove_list_[$domain])) { $awk = explode('-', $local); $local = $awk[0]; } // ・Googleアカウントについては「ドットを消す」:いるかなぁ? とも思うんだけど、可能なのでせっかくだしやっとく。これも「引っぺがすリスト」作って、後で追加対応にしておくといいのかなぁ? if (true === isset($this->dot_remove_list_[$domain])) { $local = str_replace('.', '', $local); } // return $local . '@' . $domain; } //private: private $not_lower_list_; private $plus_unremove_list_; private $hyphen_remove_list_; private $dot_remove_list_; } // end of class /* * 以下テスト */ // テスト用アドレス群 $add = [ // ノーマルなパターン 'nobody@example.com' => 'nobody@example.com', 'NObody@example.com' => 'nobody@example.com', // local-partは小文字に揃える 'no+body@example.com' => 'no@example.com', // 拡張なので除去 'no-body@example.com' => 'no-body@example.com', // ハイフンはデフォでは除去しない 'no.body@example.com' => 'no.body@example.com', // .(dot)はデフォでは除去しない '"no+body"@example.com' => 'no@example.com', // (quoted string)拡張なので除去 '"no-body"@example.com' => 'no-body@example.com', // (quoted string)ハイフンはデフォでは除去しない '"no.body"@example.com' => 'no.body@example.com', // (quoted string) .(dot)はデフォでは除去しない '"no.body@example.com' => '', // 不完全なquoted string 'no.body"@example.com' => '', // 不完全なquoted string '"nob@ody"@example.com' => 'nob@ody@example.com', // quoted stringの除去 // local-partを小文字にしない 'NObody@not-lower-example.com' => 'NObody@not-lower-example.com', // プラスを除去しない 'no+body@plus-unremove-example.com' => 'no+body@plus-unremove-example.com', '"no+body"@plus-unremove-example.com' => 'no+body@plus-unremove-example.com', // ハイフンを取り除き、プラスは除去しない 'no-body@hyphen-only-remove-example.com' => 'no@hyphen-only-remove-example.com', '"no-body"@hyphen-only-remove-example.com' => 'no@hyphen-only-remove-example.com', 'n+o-body@hyphen-only-remove-example.com' => 'n+o@hyphen-only-remove-example.com', '"n+o-body"@hyphen-only-remove-example.com' => 'n+o@hyphen-only-remove-example.com', // ハイフンもプラスも取り除く 'no-body@hyphen-remove-example.com' => 'no@hyphen-remove-example.com', '"no-body"@hyphen-remove-example.com' => 'no@hyphen-remove-example.com', 'n+o-body@hyphen-remove-example.com' => 'n@hyphen-remove-example.com', '"n+o-body"@hyphen-remove-example.com' => 'n@hyphen-remove-example.com', // dotを取り除く 'no.b.ody@gmail.example.com' => 'nobody@gmail.example.com', 'no.b.ody+test@gmail.example.com' => 'nobody@gmail.example.com', '"no.b.ody"@gmail.example.com' => 'nobody@gmail.example.com', '"no.b.ody+test"@gmail.example.com' => 'nobody@gmail.example.com', ]; // $error_count = 0; $obj = new extended_address_removal(); foreach($add as $email => $expectation_email) { $r = $obj->remove($email); if ($r === $expectation_email) { //echo "ok\n"; echo "ok: {$email} => {$r}\n"; } else { echo "ng orz\n"; //echo "{$email} => {$r} !: {$expectation_email}\n"; $error_count ++; } } if (0 === $error_count) { echo "...................ALL OK!!\n"; } else { echo "................NG( {$error_count} )!!\n"; }
とりあえずこのクラス使うと「なんとなし拡張っぽいものを一通り引っぺがせる」ので。
実装するemailアドレスは別カラムにちゃんと保存しておいてもらうとして、こいつで引っ張ってきたアドレスをUNIQUE制約したりしておくと、当初目的である
・拡張アドレスは認める
・重複したアドレスは認めない
が、ある程度いけるんではないかなぁ、と。
あとは運用でドメインを追加したりすれば、まぁまぁいけるんじゃないかなぁ、って思うざます。
…多分、そのうちMagicWeaponに追加するような気がするw