がるの健忘録 このページをアンテナに追加 RSSフィード

2016-06-23

[]牛のしぐれ煮

レシピは割とあちこち色々あるんだけど。

「笠原 将弘さん」ってあたりに信頼性があるのと(笑)、実際作って美味だったので。

散逸すると多分、死ぬるほど困るので、転記&微妙に改変。


元ネタ

http://www.kyounoryouri.jp/recipe/11621_%E7%89%9B%E8%82%89%E3%81%AE%E3%81%97%E3%81%90%E3%82%8C%E7%85%AE.html


材料

・牛:200g

・しょうが:20gってあるけど多分もっといっぱいw

・粒山椒(あれば)

*水:大さじ4

*酒:大さじ4

*しょうゆ:大さじ2弱(原本だと 大さじ1+2/3)

*みりん:大さじ2弱(原本だと 大さじ1+2/3)

*砂糖:20g


作り方

・しょうがは針ショウガにしておく

(・牛肉をさっと熱湯にくぐらせる:やったりやらなかったり)

・煮汁+針ショウガ+粒参照を軽く煮詰める

・牛肉いれて煮からめる


おかゆさん生活の時に、大変に便利でございます。

2016-06-20

[]メールアドレスの確認(前置き)

発端は「拡張メールアドレス」周りのお話し。

直接的には…まず「拡張メールアドレス弾くところあるよねぇやだよねぇ」から始まって、それに対して

・新規流入することがお金に直結するようなサイトではNG

・具体的には「新規ユーザー紹介キャンペーン」とか打つ可能性があるところ

・その対策しないとTorelloみたいに無限に自分自身を紹介して、有料会員と同じだけのサービスを受けられてしまう。

というお話しがあって。


上述の理屈としては理解できる所と理解しにくいところがあって。

理解出来るのは「馬鹿が無限に自分自身を紹介して、ってやられるのを防ぎたい」って動機。これには一編の曇りもなく、是。

ただ一方で拡張アドレスは「どこからこのメアド漏らしたねん馬鹿野郎」という意味を込めた「マーキング」としての使い方もあって、それが出来ないのはそれはそれで怖い。


ちょっと整理すると。

拡張アドレスを使うケースはおそらくいくつかあり得て

フィルタを簡単にしたい

・「個人情報流出」用ユーザ側対策

・キャンペーン等の悪用

ってあたりが、ざっくりと思いつくあたりかなぁ、と。


下段は潰したいところなんだけど、上段は微妙で(SubjectでフィルタすりゃいいじゃんとかFromでフィルタすりゃいいじゃんとか:でもいくつかのメアドで「拡張アドレスによるフィルタリング」を説明してたりするから、微妙)。

一方で中段は割と切実なので、あんまりガードされたくないの感じ(個人情報のお漏らし多いしねぇ………)。


糅てて加えて。

+付きの拡張アドレスのinvalidが「それなりに考察して練られた結果」であればまだ多少、ごくわずかに、飲み込めなくもないんだけど(濁した理由は下記参照:端的には「効果とコストのバランス」がどんなもん? ってあたり)。

可能性として「キーワード"PHP email 正規表現"でググるとよく出てくる」、いわゆる「PHP使いはもう正規表現blogに書くな」問題がありまして。

端的には「熟考の結果なの? 未熟の結果なの?」ってのが、色々と判別しにくかったりかったり。

熟考した結果なら………微妙なんだけど(完全に是とはあんまり思わないけど、完全に非って訳でもないと思うので)。

未熟の結果だとすると、色々とサービスを使う事にためらいがあったりあったりあったり。


で…上述を(大体)満たすには

・拡張アドレスは認める

・重複したアドレスは認めない

ってのが理想かなぁ。

ただまぁ、上述を言っていた子がもう一つ言っている

正しい事じゃなく儲かる事をするのが会社だからねぇ。 儲かるための正しい事なら躊躇なくやるのですよ?

という話も、わからんではない。

……がそこでちょっと疑問もある。

理解できるのは「"正しい"なんて曖昧な定義で無限にコストをかけて良い会社とかあり得ない」ってのがあって、これはYes。

ただ一方で「そのためには鐚一文すら出せないのか?」という辺りには色々と疑問もあって。

上述を言っていた子(がいる現場)は違う、ってのを前提においてなのだけど、人によっては「正しい事じゃなく儲かる事をするのが会社」のニュアンスから、安全とか(ある程度までの)法準拠とかプライバシとか、色々なものが欠落している所を見ていたりするのをそれなりに散見していると、とりあえず「コスト計算してみたいねぇ」って所が出てくる。

ってか「学習含めて一切のコストも支払わずに"儲かるのが前提で、正しさは求めない"」とか言われると、やっぱり「ちょっと怖い会社さんのサービスだよねぇ」って見解に、どうしてもなりがち。


で、端的には「じゃぁ"重複アドレスを出来るだけ除去しつつ"、"拡張アドレスは原則として認める"」の実装をやってみればいいんじゃなかろうか?

ってのが、この記事の端的な趣旨ざます。

で、あわせて「ググった正規表現、どれくらいOKでどれくらい駄目?」ってのを調べてみたのを付記いたします(こっちの方が時間コストかかってるw)。


ついでに、「email 拡張アドレス」でググると上の方に出てくる、おいちゃんの見解に近いBlogがあったので、合わせて。

http://n.h7a.org/blog/entry/1509

[]メールアドレスの確認(本当の本題:拡張アドレスの除去方法)

とりあえず「拡張アドレスの拡張部分を除去」が命題になるのですが…もうちょっと正しく書くとおそらくは違って、さらっとぶっちゃけると「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

[]メールアドレスの確認(少し本題:あちこちググってのメアド正規表現の是非)

あちこちググって、正規表現を色々と無造作にひっかけてきてみました。

で、大まかな検証結果と、前提になる「mailの規約関連」。


http://saboten009.blogspot.jp/2008/02/php_25.html

1. メールアドレスは local part と domain が @ で区切られて構成されている。(RFC2822)

2. local partは、アルファベット、数字、以下の記号 ! # $ % & ' * + - / = ? ^ _ ` { | } ~ で、ドット「 . 」で区切られている場合がある。ただし、最初、最後、他のドットの隣にあってはいけない。(つまり連続しないことということか) (RFC2822 3.2.4)

3. local partは、 クオーテーションマーク「 " 」のなかにスペースを含むいろんな文字が入った quoted string を含むことがある (RFC2822 3.2.5)

4. \@ などの "quoted pair" をlocal partに含むものも「正しい」が "obsolete" (RFC822、RFC2822 4.4) (RFC2822はRFC822をsupersedeしてobsoleteにした、とある)

5. local partの最大長は 64文字 (RFC2821 4.5.3.1)

6. domainは、labelがドットで区切られたものである (RFC1035 2.3.1)

7. domainは、アルファベットで始まり、その後に、0文字以上のアルファベット、数字、ハイフン「 - 」が続き、最後の一字はアルファベットか数字。(RFC1035 2.3.1)

8. labelの最大長は 63字 (RFC1035 2.3.1)

9. domainの最大長は 255字 (RFC2821 4.5.3.1)

10. domainは、fully qualifiedで、DNSのtype AもしくはMXレコードでresolvableでなければならない (RFC2821 3.6)

大まかに。

1番は「これを外してるのは見たことがない」ので、よいと思う。

2番が結構色々と悲喜交々で、「どの記号がいけるのか」の実装依存が強い感じ。

3番の対応は「必要だと思う」んだけど、あんまり見た記憶がないかも(苦笑

4番はそもそもobsoleteなので、あんまり気にしない。

5番はまぁ「必要ならチェックしてくれ」って感じだ。

6〜10までは正確には「ドメイン記法」だと思う…んだが、ここでも結構色々と、ある。


細かいコードは後述するとして。

大まかに…まず「メアドで各処理をチェック」してくと。


@がない

これを外す表現はさすがにないw


local-part先頭の.(ドット)はNG

割とあちこち外してる…「意図的」な可能性があるやつや、スイッチ次第で「是とも非とも」ってのもあるんだけど。

言っちゃなんだが、phpspotさんがOKなのは少しだけ意外w


local-part最後の.(ドット)はNG

「先頭dot」と微妙に結果が違うのがとても面白いw

連続した.(ドット)はNG

これはとても悩ましいところ(AUDocomoが以下略)。

なので、微妙に言及を避けますw


domain-part先頭の.(ドット)はNG

これコケる実装系があるのが不思議。

でも「コケる実装」書いてあるBlogがあるんだよ?


domain-partの最後はアルファベットまたは数字のみ

これコケる実装系があるのが不思議。ちなみにこれは「最後にアンダースコアいれた」ケース。

symfony_1さんがコケてるのが大変に意外。なんかあるのかなぁ?


domain-partの最後はアルファベットまたは数字のみ(その2)

これは「最後にdot」のケース。…ある意味正しいんだけどねぇwww

これもいくつかの実装系でコケてます。


domain-partのアンダースコアはNG

これは、厳密には少し議論が出そうなテストなんだけど。

細かい話はこちらを。

http://ya.maya.st/d/200605c.html#s20060529_2

ただまぁ「事故りそうな可能性も多々」なので、どっちかってぇと「弾く前提」のほうがいいような気がせんでもない。


nobody@example.com(普通のアドレス)

エラーが出る処理系があるこの不思議感w


0nobody@example.com (先頭を数字に)

これも謎エラーがいくつか発生。


!nobody@example.com (先頭を記号に)

エラーが増えるねぇ…


nobody@1st.example.com (ドメイン名の先頭を数字に(昔は駄目だった))

微妙な所だけど、いくつかがinvalidにしてきた。


Abc\@def@example.com (quoted pair(ただしこれはobsolete))

これは一通りエラー。まぁ順当な気もする。


!#$%&'*+-/=?^_`.{|}~@example.com (使用可能な記号の群れ)

pear、symfony_1、php_filterが優秀だなぁ。


"da.me.."@example.com (これは「quoted string」なのでvalidなパターンその1)

この辺もガチ勢の健闘が目立ちますw


"nobody@nobody"@example.com (これは「quoted string」なのでvalidなパターンその2)

大体一緒。symfony_2がNGなのが微妙に目立つかな。


nobody+regexp@gmail.com (いわゆる拡張アドレスその1)

気になるところ…いわゆる「unガチ勢」が割とこぞってNG出してきやがるのが大変に気にかかります。


nobody-regexp@gmail.com (いわゆる拡張アドレスその2)

一方でこっちは「NGがほとんど無い」あたりも、違う意味で気になります。

ideaさんが「どっちもNG」なので、ある意味あり。でもdocomoとかの携帯のアドレスって普通にハイフン入ってなかったっけ?


"nobody+regexp"@gmail.com (いわゆる拡張アドレスその3(拡張+quoted))

まぁ言わずもがなのガチ勢優勢。


"nobody-regexp"@gmail.com (いわゆる拡張アドレスその4(拡張+quoted))

いっしょ。


で、次に「処理系単位で」意図に反する動作をする子を見ていると…

unガチ勢は、割とinvalidを通す一方でvalidを通さない、っていうある意味わかりやすい状況が色々とつまびらかに。

ガチ勢は「invalidは大体はねる」「validが通らないことが稀に(でもまぁ歴史的状況的経緯もあるしなぁ…な子が通らないくらい)」。

php_filter意外が、quoted string系通さないのが、意外なのか順当なのか。


まぁかように「unガチ勢ほど拡張アドレスをNGにしやすい」ので。

もちろん「ちゃんと分かった上で丁寧に書いている」所もあろうかとは思うんだけど……割と定期的に「駄目な正規表現コードを拝見したり」、あまつさえ「プログラムなんてググってコピペすればいいんだよ」とかいう話を見たり聞いたりすると、色々ともにょったりするわけなのです。


blogからコピペしてもいいけど、コードの読み込みと検証はちゃんとやろうね!!

[]メールアドレスの確認(余談? 本題? ググったコードの実検証)

実コードです。

後で修正いれる可能性があるんで、気づいた事とかあったら突っ込んでくださいませ。

<?php

// http://phpspot.net/php/pg%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%EF%BC%9A%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E8%AA%BF%E3%81%B9%E3%82%8B.html
function phpspot($text) {
  if (preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $text)) {
    return true;
  } else {
    return false;
  }
}
// http://www.ideaxidea.com/archives/2009/03/practical_php_regexs.html
function idea($email) {
  if (preg_match('/^[^0-9][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[@][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[.][a-zA-Z]{2,4}$/',$email)) {
    return true;
  } else {
    return false;
  }
}

//http://qiita.com/tabo_purify/items/aa279cd28abdeba0873c
function tabo_purify($mail) {
  if (preg_match('|^[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+$|', $mail)) {
    return true;
  }else{
    return false;
  }
}
function doli($email) {
  if ( preg_match('/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/i', $email) ){
    return true;
  } else {
    return false;
  }
}

// http://www.seesaa.hitsug.net/article/406595659.html
function seesaa($email){
  if(preg_match('/^[a-zA-Z0-9_.!~*\'();\/?:\&=+\$,%#-]+@[A-Za-z0-9_.-]+\.[A-Za-z0-9]+$/', $email)){
    return true;
  } else {
    return false;
  }
}

// http://d.hatena.ne.jp/yachin29/20130410/p1
function yachin29($email){
  if (preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $email)) {
    return true;
  } else {
    return false;
  }
}

// http://doremi.s206.xrea.com/php/tips/preg.html
function doremi_1($email) {
  if (preg_match('/^[\w\-\.]+@[\w\-\.]+$/D', $email)) {
    return true;
  }
  //else
  return false;
}
function doremi_2($email) {
  if (preg_match('/^\w+(?:[\.\-]\w+)*@\w+(?:-\w+)?(?:\.\w+(?:-\w+)?)+$/D', $email)) {
    return true;
  }
  //else
  return false;
}

// http://qiita.com/mpyw/items/257eabe0b43b1e02e6f7
function mpyw($email, $strict = true) {
    $dot_string = $strict ?
        '(?:[A-Za-z0-9!#$%&*+=?^_`{|}~\'\\/-]|(?<!\\.|\\A)\\.(?!\\.|@))' :
        '(?:[A-Za-z0-9!#$%&*+=?^_`{|}~\'\\/.-])'
    ;
    $quoted_string = '(?:\\\\\\\\|\\\\"|\\\\?[A-Za-z0-9!#$%&*+=?^_`{|}~()<>[\\]:;@,. \'\\/-])';
    $ipv4_part = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
    $ipv6_part = '(?:[A-fa-f0-9]{1,4})';
    $fqdn_part = '(?:[A-Za-z](?:[A-Za-z0-9-]{0,61}?[A-Za-z0-9])?)';
    $ipv4 = "(?:(?:{$ipv4_part}\\.){3}{$ipv4_part})";
    $ipv6 = '(?:' .
        "(?:(?:{$ipv6_part}:){7}(?:{$ipv6_part}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){6}(?::{$ipv6_part}|:{$ipv4}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){5}(?:(?::{$ipv6_part}){1,2}|:{$ipv4}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){4}(?:(?::{$ipv6_part}){1,3}|(?::{$ipv6_part})?:{$ipv4}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){3}(?:(?::{$ipv6_part}){1,4}|(?::{$ipv6_part}){0,2}:{$ipv4}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){2}(?:(?::{$ipv6_part}){1,5}|(?::{$ipv6_part}){0,3}:{$ipv4}|:))" . '|' .
        "(?:(?:{$ipv6_part}:){1}(?:(?::{$ipv6_part}){1,6}|(?::{$ipv6_part}){0,4}:{$ipv4}|:))" . '|' .
        "(?::(?:(?::{$ipv6_part}){1,7}|(?::{$ipv6_part}){0,5}:{$ipv4}|:))" . 
    ')';
    $fqdn = "(?:(?:{$fqdn_part}\\.)+?{$fqdn_part})";
    $local = "({$dot_string}++|(\"){$quoted_string}++\")";
    $domain = "({$fqdn}|\\[{$ipv4}]|\\[{$ipv6}]|\\[{$fqdn}])";
    $pattern = "/\\A{$local}@{$domain}\\z/";
    return preg_match($pattern, $email, $matches) &&
        (
            !empty($matches[2]) && !isset($matches[1][66]) && !isset($matches[0][256]) ||
            !isset($matches[1][64]) && !isset($matches[0][254])
        )
    ;
}

// http://fdays.blogspot.jp/2007/10/rfc-2822-j0hn-d0e-10-pregmatch-9.html
function fdays_1($m) {
  //local part
  $atext = "[-!#-'*+/-9=?^-~]";
  $atom = $atext . '+';
  $dot_atom = $atom . '(//.' . $atom . ')*'; //2

  $qtext = '[]-~!#-[]';
  $text = '[ -~]';
  $quoted_pair = '////' . $text; //4
  $quoted_string = '"(' . $qtext . '|' . $quoted_pair . ')*"'; //3

  $local_part = '(' . $dot_atom . '|' . $quoted_string . ')';

  //domain
  $label = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?'; //7, 8
  $domain = $label . '(//.' . $label . ')*//.([a-z]{2,4}|museum)'; //6

  //全体
  $addr_spec = $local_part . '@' . $domain; //1

  $regexp = '/^' . str_replace('/', '\\/', $addr_spec) . '$/i';

  if (preg_match($regexp, $m)) {
    return true;
  }
  //else
  return false;
}
function fdays_2($value) {
  if (preg_match('/^[-+\\w]+(\\.[-+\\w]+)*@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]{2,6}$/i', $value)) {
    return true;
  }
  //else
  return false;
}
function fdays_3($value) {
  if (preg_match('/^[-+.\\w]+@[-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]{2,6}$/i', $value)) {
    return true;
  }
  //else
  return false;
}


// ---------------------------------------------

// http://catbot.net/blog/2007/06/re_php.html
// ソースは PEARから直接( http://download.pear.php.net/package/Mail-1.3.0.tgz )
function pear($data, $strict = false)
{
    $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
    if (preg_match($regex, trim($data), $matches)) {
        //return array($matches[1], $matches[2]);
        return true;
    } else {
        return false;
    }
}


// http://catbot.net/blog/2007/06/re_php.html


// 多分こういう関数だと思う
define('VALID_EMAIL', '/\\A(?:^([a-z0-9][a-z0-9_\\-\\.\\+]*)@([a-z0-9][a-z0-9\\.\\-]{0,63}\\.(com|org|net|biz|info|name|net|pro|aero|coop|museum|[a-z]{2,4}))$)\\z/i');
function cake($s) {
    if (preg_match(VALID_EMAIL, $s, $matches)) {
        //return array($matches[1], $matches[2]);
        return true;
    } else {
        return false;
    }

}
//
function Ethna($mailaddress)
{
    if (preg_match('/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i', $mailaddress)) {
        return true;
    }
    return false;
}
// XXX 第二引数は省略
// XXX classメソッドぽいので、$thisをcallするあたり省略
// ドメインチェックは(今回の趣旨から外れるし)省略
function symfony_1(&$value /*, &$error*/) {
  return _symfony($value, false);
}
function symfony_2(&$value /*, &$error*/) {
  return _symfony($value, true);
}
function _symfony(&$value, $strict)
{
  $error; // XXX
  //$strict = $this->getParameterHolder()->get('strict');
  if ($strict == true)
  {
    $re = '/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i';
  }
  else
  {
    /* Cal Henderson: http://iamcal.com/publish/articles/php/parsing_email/pdf/
     * The long regular expression below is made by the following code
     * fragment:
     *
     *   $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
     *   $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
     *   $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'
     *         . '\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
     *   $quoted_pair = '\\x5c\\x00-\\x7f';
     *   $domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
     *   $quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
     *   $domain_ref = $atom;
     *   $sub_domain = "($domain_ref|$domain_literal)";
     *   $word = "($atom|$quoted_string)";
     *   $domain = "$sub_domain(\\x2e$sub_domain)*";
     *   $local_part = "$word(\\x2e$word)*";
     *   $addr_spec = "$local_part\\x40$domain";
     */

    $re = '/^([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-'
         .'\\x5d\\x7f-\\xff]+|\\x22([^\\x0d\\x22\\x5c\\x80-\\xff]|\\x5c\\x00-'
         .'\\x7f)*\\x22)(\\x2e([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-'
         .'\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x22([^\\x0d\\x22\\x5c\\x80'
         .'-\\xff]|\\x5c\\x00-\\x7f)*\\x22))*\\x40([^\\x00-\\x20\\x22\\x28\\x29'
         .'\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x5b([^'
       .'\\x0d\\x5b-\\x5d\\x80-\\xff]|\\x5c\\x00-\\x7f)*\\x5d)(\\x2e([^\\x00-'
         .'\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-'
         .'\\xff]+|\\x5b([^\\x0d\\x5b-\\x5d\\x80-\\xff]|\\x5c\\x00-\\x7f)*'
         .'\\x5d))*$/'
    ;
  }

  if (!preg_match($re, $value))
  {
    //$error = $this->getParameterHolder()->get('email_error');
    return false;
  }

/*
  $checkDomain = $this->getParameterHolder()->get('check_domain');
  if ($checkDomain && function_exists('checkdnsrr'))
  {
    $tokens = explode('@', $value);
    if (!checkdnsrr($tokens[1], 'MX') && !checkdnsrr($tokens[1], 'A'))
    {
      $error = $this->getParameterHolder()->get('email_error');

      return false;
    }
  }
*/
  return true;
} 

// PHP
function php_filter($email) {
  $r = filter_var($email, FILTER_VALIDATE_EMAIL);
  if (false === $r) {
    return false;
  }
  // else
  return true;
}

/*
// 未テストだけど本来的にvalid
// 「お名前 <アドレス>」は、うち(MagicWeapon)には「引っぺがすメソッド」あるから一旦無視
<foo@example.com>
foo <foo@example.com>
// 「ドメインpartにIPアドレス指定」は今回の趣旨から外れるから無視
canada@[192.168.0.1]
*/

// 本来invalid
$invalid = [
  'dameexample.com' => '@がない',
  '.da.me@example.com' => 'local-part先頭の.(ドット)はNG',
  'da.me.@example.com' => 'local-part最後の.(ドット)はNG',
  'da..me@example.com' => '連続した.(ドット)はNG',
  'da.me@.example.com' => 'domain-part先頭の.(ドット)はNG',
  'da.me@example.com_' => 'domain-partの最後はアルファベットまたは数字のみ',
  'da.me@example.com.' => 'domain-partの最後はアルファベットまたは数字のみ(その2)',
  'nobody@add_example.com' => 'domain-partのアンダースコアはNG',
];

// 本来valid
$valid = [
  'nobody@example.com' => '普通のアドレス',
  '0nobody@example.com' => '先頭を数字に',
  '!nobody@example.com' => '先頭を記号に',
  'nobody@1st.example.com' => 'ドメイン名の先頭を数字に(昔は駄目だった)',
  'Abc\@def@example.com' => 'quoted pair(ただしこれはobsolete)',
  "!#$%&'*+-/=?^_`.{|}~@example.com" => '使用可能な記号の群れ',
  '"da.me.."@example.com' => 'これは「quoted string」なのでvalidなパターンその1',
  '"nobody@nobody"@example.com' => 'これは「quoted string」なのでvalidなパターンその2',
  'nobody+regexp@gmail.com' => 'いわゆる拡張アドレスその1',
  'nobody-regexp@gmail.com' => 'いわゆる拡張アドレスその2',
  '"nobody+regexp"@gmail.com' => 'いわゆる拡張アドレスその3(拡張+quoted)',
  '"nobody-regexp"@gmail.com' => 'いわゆる拡張アドレスその4(拡張+quoted)',
];

$funcs = [
  'phpspot',
  'idea',
  'tabo_purify',
  'doli',
  'seesaa',
  'yachin29',
  'doremi_1',
  'doremi_2',
  'mpyw',
  // 'fdays_1', valid系が全部エラーになるので、一旦未使用にする
  'fdays_2',
  'fdays_3',
  'pear',
  'cake',
  'Ethna',
  'symfony_1',
  'symfony_2',
  'php_filter',
];

// ぶん回し: メールアドレス単位での「各処理」の具合
foreach(['invalid' => false, 'valid' => true] as $data => $expected_ret) {
echo "{$data}\n";
  foreach($$data as $email => $comment) {
    echo "  {$email} ({$comment})\n";
    foreach ($funcs as $func) {
      printf("    call %-15s:", $func);
      $r = $func($email);
      if ($r === $expected_ret) {
        echo "... OK!!\n";
      } else {
        echo "... NG orz\n";
      }
    }
    echo "\n";
  }
  echo "\n\n";
}

// ぶん回し:各処理単位での、メアドの具合
// ついでにカウント
$count = [];
foreach ($funcs as $func) {
  $count[$func] = ['OK' => 0, 'NG' => 0];
  printf("call %-15s\n", $func);
  foreach(['invalid' => false, 'valid' => true] as $data => $expected_ret) {
    echo "  {$data}\n";
    foreach($$data as $email => $comment) {
      //printf("    %-30s", $email);
      $r = $func($email);
      if ($r === $expected_ret) {
        //echo "... OK!!\n";
        $count[$func]['OK'] ++;
      } else {
        printf("    %-30s... NG orz(%s)\n", $email, $comment);
        //echo "... NG orz({$comment})\n";
        $count[$func]['NG'] ++;
      }
    }
  }
  echo "\n";
}

//
foreach($count as $func => $types) {
  echo "{$func}\n";
  foreach($types as $type => $i) {
    printf("    %s => %d\n", $type, $i);
  }
}

結果


invalid

 dameexample.com (@がない)

  call phpspot    :... OK!!

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... OK!!

  call doremi_1    :... OK!!

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 .da.me@example.com (local-part先頭の.(ドット)はNG)

  call phpspot    :... OK!!

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... OK!!

  call seesaa     :... NG orz

  call yachin29    :... OK!!

  call doremi_1    :... NG orz

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... OK!!

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... NG orz

  call php_filter   :... OK!!

 da.me.@example.com (local-part最後の.(ドット)はNG)

  call phpspot    :... NG orz

  call idea      :... OK!!

  call tabo_purify  :... NG orz

  call doli      :... OK!!

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... NG orz

  call php_filter   :... OK!!

 da..me@example.com (連続した.(ドット)はNG)

  call phpspot    :... NG orz

  call idea      :... OK!!

  call tabo_purify  :... NG orz

  call doli      :... OK!!

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... NG orz

  call php_filter   :... OK!!

 da.me@.example.com (domain-part先頭の.(ドット)はNG)

  call phpspot    :... OK!!

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... NG orz

  call yachin29    :... OK!!

  call doremi_1    :... NG orz

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 da.me@example.com_ (domain-partの最後はアルファベットまたは数字のみ)

  call phpspot    :... NG orz

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... NG orz

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 da.me@example.com. (domain-partの最後はアルファベットまたは数字のみ(その2))

  call phpspot    :... NG orz

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 nobody@add_example.com (domain-partのアンダースコアはNG)

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... NG orz

  call symfony_1   :... NG orz

  call symfony_2   :... OK!!

  call php_filter   :... OK!!



valid

 nobody@example.com (普通のアドレス)

  call phpspot    :... OK!!

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... OK!!

  call doremi_1    :... OK!!

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 0nobody@example.com (先頭を数字に)

  call phpspot    :... OK!!

  call idea      :... NG orz

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... OK!!

  call doremi_1    :... OK!!

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 !nobody@example.com (先頭を記号に)

  call phpspot    :... NG orz

  call idea      :... OK!!

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... OK!!

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... OK!!

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 nobody@1st.example.com (ドメイン名の先頭を数字に(昔は駄目だった))

  call phpspot    :... OK!!

  call idea      :... OK!!

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... OK!!

  call doremi_1    :... OK!!

  call doremi_2    :... OK!!

  call mpyw      :... NG orz

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 Abc\@def@example.com (quoted pair(ただしこれはobsolete))

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... NG orz

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... NG orz

  call symfony_2   :... NG orz

  call php_filter   :... NG orz

 !#$%&'*+-/=?^_`.{|}~@example.com (使用可能な記号の群れ)

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... OK!!

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 "da.me.."@example.com (これは「quoted string」なのでvalidなパターンその1)

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 "nobody@nobody"@example.com (これは「quoted string」なのでvalidなパターンその2)

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... NG orz

  call php_filter   :... OK!!

 nobody+regexp@gmail.com (いわゆる拡張アドレスその1)

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 nobody-regexp@gmail.com (いわゆる拡張アドレスその2)

  call phpspot    :... OK!!

  call idea      :... NG orz

  call tabo_purify  :... OK!!

  call doli      :... OK!!

  call seesaa     :... OK!!

  call yachin29    :... OK!!

  call doremi_1    :... OK!!

  call doremi_2    :... OK!!

  call mpyw      :... OK!!

  call fdays_2    :... OK!!

  call fdays_3    :... OK!!

  call pear      :... OK!!

  call cake      :... OK!!

  call Ethna     :... OK!!

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 "nobody+regexp"@gmail.com (いわゆる拡張アドレスその3(拡張+quoted))

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!

 "nobody-regexp"@gmail.com (いわゆる拡張アドレスその4(拡張+quoted))

  call phpspot    :... NG orz

  call idea      :... NG orz

  call tabo_purify  :... NG orz

  call doli      :... NG orz

  call seesaa     :... NG orz

  call yachin29    :... NG orz

  call doremi_1    :... NG orz

  call doremi_2    :... NG orz

  call mpyw      :... OK!!

  call fdays_2    :... NG orz

  call fdays_3    :... NG orz

  call pear      :... NG orz

  call cake      :... NG orz

  call Ethna     :... NG orz

  call symfony_1   :... OK!!

  call symfony_2   :... OK!!

  call php_filter   :... OK!!



call phpspot    

 invalid

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

  da.me@example.com_      ... NG orz(domain-partの最後はアルファベットまたは数字のみ)

  da.me@example.com.      ... NG orz(domain-partの最後はアルファベットまたは数字のみ(その2))

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call idea     

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  0nobody@example.com      ... NG orz(先頭を数字に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  nobody-regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call tabo_purify  

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call doli     

 invalid

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call seesaa    

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

  da.me@.example.com      ... NG orz(domain-part先頭の.(ドット)はNG)

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call yachin29   

 invalid

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

  da.me@example.com_      ... NG orz(domain-partの最後はアルファベットまたは数字のみ)

  da.me@example.com.      ... NG orz(domain-partの最後はアルファベットまたは数字のみ(その2))

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call doremi_1   

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

  da.me@.example.com      ... NG orz(domain-part先頭の.(ドット)はNG)

  da.me@example.com_      ... NG orz(domain-partの最後はアルファベットまたは数字のみ)

  da.me@example.com.      ... NG orz(domain-partの最後はアルファベットまたは数字のみ(その2))

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call doremi_2   

 invalid

  da.me@example.com_      ... NG orz(domain-partの最後はアルファベットまたは数字のみ)

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  nobody+regexp@gmail.com    ... NG orz(いわゆる拡張アドレスその1)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call mpyw     

 invalid

 valid

  nobody@1st.example.com    ... NG orz(ドメイン名の先頭を数字に(昔は駄目だった))

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

call fdays_2    

 invalid

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call fdays_3    

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call pear     

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

 valid

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call cake     

 invalid

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call Ethna     

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  !nobody@example.com      ... NG orz(先頭を記号に)

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  !#$%&'*+-/=?^_`.{|}~@example.com... NG orz(使用可能な記号の群れ)

  "da.me.."@example.com     ... NG orz(これは「quoted string」なのでvalidなパターンその1)

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

  "nobody+regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその3(拡張+quoted))

  "nobody-regexp"@gmail.com   ... NG orz(いわゆる拡張アドレスその4(拡張+quoted))

call symfony_1   

 invalid

  da.me@example.com_      ... NG orz(domain-partの最後はアルファベットまたは数字のみ)

  nobody@add_example.com    ... NG orz(domain-partのアンダースコアはNG)

 valid

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

call symfony_2   

 invalid

  .da.me@example.com      ... NG orz(local-part先頭の.(ドット)はNG)

  da.me.@example.com      ... NG orz(local-part最後の.(ドット)はNG)

  da..me@example.com      ... NG orz(連続した.(ドット)はNG)

 valid

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

  "nobody@nobody"@example.com  ... NG orz(これは「quoted string」なのでvalidなパターンその2)

call php_filter  

 invalid

 valid

  Abc\@def@example.com     ... NG orz(quoted pair(ただしこれはobsolete))

phpspot

  OK => 7

  NG => 13

idea

  OK => 9

  NG => 11

tabo_purify

  OK => 9

  NG => 11

doli

  OK => 13

  NG => 7

seesaa

  OK => 9

  NG => 11

yachin29

  OK => 7

  NG => 13

doremi_1

  OK => 5

  NG => 15

doremi_2

  OK => 10

  NG => 10

mpyw

  OK => 18

  NG => 2

fdays_2

  OK => 13

  NG => 7

fdays_3

  OK => 10

  NG => 10

pear

  OK => 12

  NG => 8

cake

  OK => 11

  NG => 9

Ethna

  OK => 9

  NG => 11

symfony_1

  OK => 17

  NG => 3

symfony_2

  OK => 15

  NG => 5

php_filter

  OK => 19

  NG => 1

2016-06-19

[][]phpのsession関数を使ってのギミック

PHPセッションは、便利な反面色々と気になる事もあるのですが。

そのうち

・「セッション有効時間」をきっちりしたい(有効時間が切れたら問答無用で切断したい)

アカウントロックの実装をかけて、ロックしたら「ログイン出来ない」だけじゃなくて「現在ログイン中のアカウントもシャット」したい

のあたりを解決しようかなぁ、と思いました。


細かい所をかくと

・「自分のアカウントに紐付いているセッションIDのうち、現在のセッション以外を全部遮断」(gmailの「アカウント アクティティ」の「他のすべてのWebセッションからログアウト」)

も実装したいのだけど…ここに手を入れるのは些か難儀なので、一旦見送り。


で、実装方法を簡単に。

基本は、いわゆる「認可(authorization):ログイン中のユーザかどうか」の判断ロジックのところで、少しギミックを入れました。

その手前として、ログイン(認証:authentication)のタイミングでも、ギミック追加しています。


寿命については、大まかには

ログイン時に、$_SESSIONに「有効時間」を入れる

・認可の度に

・・有効時間を超えてたら問答無用でログアウトさせる

・・有効時間範囲内なら「有効時間を更新して」認可する

ってロジック


また合わせて、認可のタイミングのラスト付近で「ロックテーブル」なるテーブルを1枚用意して。最低限としては「ユーザID」だけがカラムにあればよくて、判断としては

・ロックテーブルに「認可を試みてるユーザのユーザIDが存在してたら、認可しない」

って感じで「問答無用でロック」が出来るようにしました。

DBアクセスが1つ増えるのが微妙っちゃぁ微妙なんだけど、まぁその辺はトレードオフかなぁ、と。

基本「KVS的なアクセス」でよいので、その辺で色々と小細工をしてみてもよいだろうし。


たいした実装でもないのですが、色々と「ちょっともにょってた」ところが実装できたので、割と満足でございます。


実装サンプルはこちらからどんぞ。

https://github.com/gallu/MagicWeaponTest

の「lib/common/base_model_admin_base.inc」と、

https://github.com/gallu/MagicWeapon

の「authorization_session.inc」

あたりを追いかけていくと、大体、処理が点在しています(笑


あとは…微妙っちゃぁ微妙なのですが

・そもそも「Anonymouse セッション(ログインしてなくても始まるセッション)」ってどうなんだろう?(セッションアダプションはやっぱりちょっと気になる)

・「他のすべてのWebセッションからログアウト」入れたいよねぇ

あたりになると、セッション周りを「自作しなおすかねぇ( http://d.hatena.ne.jp/gallu/20160402/p1 )」という話になって。

ただ、この辺になると上述で言うところの「authorization_session.inc」の処理にも手を加える(ってか多分、別クラスにする)ってのもあって…ってのが、「セッションIDを作ったり設定したりする」辺りを全部自前実装にする気ぃ満々なのでw

興味はあるんだけど少し時間がかかりそうで……次回短期入院の時の課題かなぁ、とw


まぁ「なければ作る」はUNIX系列の伝統的作法だと思うので。

便利なPHPの機能は最大限「そのまま使いつつ」、不便な所をラップしていくのが最近のおいちゃんの好みかなぁ、という辺りでの邁進を想定しておりまするる。

2016-06-14

[]「配列から一部の要素を削る」のはどっちが早い?

いやまぁ直接的には、うちのフレームワーク(MagicWeapon)で使う系のコードのお話なのですがw

「値が重複しない前提の配列」から、「特定の値を引き算する」方法とその計測です。


とりあえず手っ取り早いところで、PHPにはarray_diffって関数があるのですが( http://php.net/manual/ja/function.array-diff.php )。

微妙に気になるのが「多分、線形検索やってるよなぁ」ってところ。

いやまぁそんなに多大に気にしても仕方がないような気がするのですが。


で、おいちゃんが割と使うのが。「値が重複しない前提」があるのと「順番は無関係」ってのもあるので。

array_flip( http://php.net/manual/ja/function.array-flip.php )で反転してkeyをunsetしてもう一回array_flipすれば、線形検索はいらなくない? って思ったあたり。

反証としては「hash値の計算で時間がかかる」「flipした時の作業用の配列の作成とGCに時間がかかる」あたり。


この辺は検証したほうが早いので、さっそく検証。

まずコード

<?php

ini_set('display_errors', 1);
error_reporting(-1);

$base = 'a';
$awk = array();
for($i = 0; $i < 10000; $i ++) {
  $awk[] = $base ++;
}
//var_dump($awk);

$t = microtime(true);
for($i = 0; $i < 100; $i ++) {
  // array_diff
  $awk2 = array_diff($awk, array('ntp', 'gjh', 'jwp', 'col'));

  // array_flip
/*
  $awk2 = array_flip($awk);
  unset($awk2['ntp']);
  unset($awk2['gjh']);
  unset($awk2['jwp']);
  unset($awk2['col']);
  $awk2 = array_flip($awk2);
*/
}
$t = microtime(true) - $t;
var_dump($t);

上述コードで、コメントアウト適当に操作して「diffとflip」とで検証。


まずPHP5.5系。

diff

float(2.1626000404358)

float(1.5443460941315)

float(1.647617816925)

float(1.7004590034485)

float(1.635516166687)

float(1.6240890026093)

float(1.6233410835266)

flip

float(1.3751840591431)

float(1.4717681407928)

float(1.4122080802917)

float(1.3495209217072)

float(1.403538942337)

float(1.4738049507141)

float(1.4681560993195)


次にPHP7.0系

diff

float(0.088997840881348)

float(0.081218004226685)

float(0.084403038024902)

float(0.085947036743164)

float(0.071822166442871)

float(0.084525108337402)

float(0.087594985961914)

flip

float(0.068278074264526)

float(0.074533939361572)

float(0.08522891998291)

float(0.075110912322998)

float(0.073028087615967)

float(0.078052997589111)

float(0.08232593536377)


…おかしいマシンパワーそんなに変わらないはずなのに、桁が違う(苦笑

さすがPHP7系、配列周りのチューニング半端ない


7系、forの回数を1000回にして追試。

diff

float(0.54727816581726)

float(0.64334392547607)

float(0.54209017753601)

float(0.53981614112854)

flip

float(0.80930995941162)

float(0.68021702766418)

float(0.68110299110413)

float(0.81561899185181)


…をや?

履行回数を増やすと、flipの成績が悪化していく。

やぱり「配列領域の確保と解放」に時間がかかってるのかしらん?

いくつか追試をしてみたですが、全体的な傾向として「履行回数が少ないとflip有利」「履行回数が多いとdiff有利」なので、やぱり領域確保&解放にそれなりのコストが費やされてる悪寒。


むつかしいなぁ正直。

こまか〜〜〜く話をすると「そんなに何回もぶん回す処理ではない」ので、それを考えると一応「flip有利」ではあるんだけど。

微々たるもんでもあるので、diffのほうが「見た目シンプルでわかりやすい」気がしないでもない。

ちなみにメモリは、PHP7では「どっちも一緒」、PHP5.5では「flipのほうが少しメモリ使用量が多い」感じでした。

つまり「PHP7前提ならこれもまた差異がない」、と。


悩ましいなぁ…でもまぁ「見た目シンプルでわかりやすい」前提だと、diffのほうがいいのかしらん? 的な気がしないでもない。

当初使用想定だと、要素数なんていっても100未満である可能性が高いので、そうすると線形検索ったって大してCPUコストかからないだろうしねぇ。


という、微妙に煮え切らない結果でしたw

2016-06-10

[][]data_clumpの使い方(ざっくり版)

ほぼ私信のようなもんですがw

細かくはまたマニュアル( https://github.com/gallu/MagicWeaponManual/blob/master/table_of_contents.md )のほうに書きますが、一端、メモ程度に。


本質的なところ

data_clumpは「データの塊」です。

例えば「1つのmail form」は一塊のデータだと思われますし、DBの1テーブルなんてのも、一塊のデータだと思われます。

そんな「一塊」ごとに1クラスをアサインしているのがdata_clumpです。


下ごしらえ

とりあえず「data_clump継承クラス」を作成してください。

場所は、MagicWeaponの流儀をそのまま持ち込むのであれば、libディレクトリにclumpってディレクトリを切って、そこに置くとよいです。

また必須ではないのですが、clumpディレクトリの中にbaseってディレクトリを切っておくと、「Generation Gapパターン」的な意味でよりよいです。


例えば。

http://furu.mwtest.gjmj.net/

で動きを見ていただけて、ソースコード

https://github.com/gallu/MagicWeaponTest

にあるのですが。

「1つのform」を構成するクラスは、

https://github.com/gallu/MagicWeaponTest/blob/master/lib/clump/base/form_test_clump_base.inc

https://github.com/gallu/MagicWeaponTest/blob/master/lib/clump/form_test_clump.inc

の2つのクラスに分解すると、色々とはかどります。


「clump/base/*_clump_base.inc」は、大まかには「このデータの塊は具体的にどんな要素をもっているのか」が記述してあります。

「clump/*_clump_base.inc」は、data_clumpの本体になります。

formだと難しいのですが、「DBテーブル」単位でclumpを作る場合は、MagicWeaponをインストールしたディレクトリのtool/soak_up_information.php( https://github.com/gallu/MagicWeapon/blob/master/tools/soak_up_information.php )ってのを叩くと、自動でコードを生成してくれます。

もうちょっと正確に書くと「baseは常に上書きで生成(するからこのファイルは追記とかしないようにしたほうが無難)」「clumpは"なければ作る"」って動きをします。


以下、上述のファイルを作ってる前提で話をすすめます。


formからのデータの取得とかvalidateとか出力とかその辺

ものすごく大まかには、以下のようなコードで大体処理をします。

https://github.com/gallu/MagicWeaponTest/blob/master/lib/form_confirm.inc

(ちょっとコメント変えてます)

  // data_clumpインスタンスを取得
  $form_test_c = $this->get_clump('form_test_clump');

  // 「cgi requestから」データを取得
  $form_test_c->set_from_cgi($this->get_cgi_request());

  // validate(定型のみ)
  if (false === $form_test_c->is_valid($this->get_conv())) {
    $this->forward('form_input');
    return ;
  }

  // セッションに保存
  $_SESSION['form_test'] = $form_test_c->get_all_data_to_hash();

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

インスタンスは「newで作る」でもよいのですが、MagicWeaponのMVCを使っている場合は「model(ほかのフレームワークのcontroller/action)から取得可能」なので、そこで取得してます。

base_model#get_clump()で取得すると「newした後でDBハンドルぶち込んどいてくれる」程度に便利です…が、上述のような「formからの取得だけ」なら、あんまり意味はないです(笑

まぁ「他とそろえた方が見やすい」程度かなぁ。


data_clumpは「一塊のデータ」なので「cgi requestから、一塊を一式取得しといて」は、1メソッド set_from_cgi() で片付けます。

同様に、validateも「どう? valid?」なので、1メソッド

ちなみにvalidateについては、「定型で片付く」範疇までなら、protected function set_element()ん中のpush_validateで定数とか設定しておけば、後は自動。

「もうちょっとややこしいvalidateが絡む」場合は、is_valid_insert()またはis_valid_update()を上書きします。先にparent::で「定型処理のvalidate」呼んでもらって、残りの「ややこい」のは自力で適宜追記実装してください。


validaだった場合。

data_clumpは、内部的に「data」と「view_values」を微妙に使い分けているので、使い分けながら「セッションに情報を保存しつつ」「表示しつつ」します。

dataは「内部データ構造」。なので、DBとのやりとりとか、いわゆる「生データ」がここになります。

一方でview_valuesは「表示用のデータ」。

dataと等しいケースもあるけど「表示用にちょっと色々小細工してみたい」なんてケースもあるので、その場合はview_valuesメソッドを上書きして、parent::で情報取得した後に「小細工いれてreturn」とかやると、色々と便利です。


で、データを最終的に受けとるのはこっち。

https://github.com/gallu/MagicWeaponTest/blob/master/lib/form_fin.inc

  // clumpに一度通してからviewへ
  $form_test_c = $this->get_clump('form_test_clump');
  $form_test_c->set_all_data_from_hash($_SESSION['form_test']);

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

表示するので、言い方を変えると「view_valuesの処理を通したい」ので。

set_all_data_from_hash()で「生データをぶち込んで」から、get_all_view_values()でviewに変数を一式セット。

これを定型でやっておくと「なんか表示上の変更があっても、get_all_view_values()とテンプレート修正すればOK」なので、DRYに近い感じでよいのです。


上述コードは「ほかはなんもやってない」のですが、実際には、mailを作って送ったり、とかするんだと思う。

get_all_view_values()があるからあんまりこの状態だと使わない気がするんだけど、例えば上述で「text_dataのデータ単体が欲しい」場合は、「get_value('text_data')」で取得可能。


formからのデータの取得をDBにぶち込む系

大体いっしょ。

サンプルコードがないのでざっくり概念だけ書くと、finのタイミングで

  // clumpに一度通してからviewへ
  $form_test_c = $this->get_clump('form_test_clump');
  $form_test_c->set_all_data_from_hash($_SESSION['form_test']);

  // DBにinsert
  $r = $form_test_c = $this->insert();
  if (false === $r) {
    // insertできなかったお orz
  }

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

これくらい。

set()ってメソッドもあって、いわゆるupsertなんだけど、最近あんまり使わないようにしてるので多分そのうち非推奨になる予定w

ちなみにinsertの場合、「set_insert_date_name()」ってのが設定されていると、設定されたカラム名に「現在日付」が入る感じ。


で…DBにinsertする場合。

set_from_cgi(正確にはset_from_cgi_insert)でデータを取得するときに、例えば「idはauto incrementなのに、パラメタインジェクションで外部から指定されちゃってショック!!」ってのが、起きないとも限らない。

なので、そーゆー時はset_from_cgi_insert()を上書きしてくださいませ。

set_from_cgi_insertは内部的に「set_from_cgi_detailをcallしている」だけで、set_from_cgi_detailは

 * @param cgi_request $req cgi_requestクラスのインスタンス
 * @param vector $target 対象とするcgi name attribute値
 * @param boolean $empty_overwrite_flg 空文字の上書きフラグ trueにすると空なら空文字を上書きする

なので。

第二引数の「$target」から、例えば「自動で設定したいPKカラム名」を抜いたりすればOK。

$this->get_all_names() で「全パラメタ名」が配列で取得できるので、そこから「抜きたいのを削除する」ってロジックを書くと楽。

おいちゃんは

・array_flipで値とkeyを反転

・unsetでkeyを指定して削除したいカラム名を削除

・array_flipで戻す

ってやり方をよくやるかな。

これなら「後でカラム名が増えても」気にせずやっていけるからw


update系

処理としては似てるんだけど。

やり方的には

keyに対応する情報をDBから取得

cgi requestで「修正すべき値」が投げられてくる

DBをupdate

って手順になると思う。


雑にソースコードを書くと

  // clumpインスタンス作成
  $hoge_c = $this->get_clump('hoge_clump');

  // keyを設定
  // XXX IDが空かどうかのチェックは省略
  $hoge_c->set_value('hoge_id', $this->get_cgi_request()->find('hoge_id'));

  // DBから取得
  $r = $hoge_c->get();
  if (false === $r) {
    // データないってよ!!
    適宜エラー処理各種
    return ;
  }

  // 表示
  $this->get_conv()->set('hoge', $hoge_c->get_all_view_values());

で、まず表示。

修正内容取得&(確認画面抜きにして)修正なら


  // clumpインスタンス作成
  $hoge_c = $this->get_clump('hoge_clump');

  // 「cgi requestから」データを取得
  $hoge_c->set_from_cgi_update($this->get_cgi_request());

  // validate(定型のみ)
  if (false === $hoge_c->is_valid_update($this->get_conv())) {
    適宜エラー処理各種
    return ;
  }

  // DBの内容編集
  $hoge_c->update();

  // 表示
  $this->get_conv()->set('hoge', $hoge_c->get_all_view_values());

こんな感じ。

set_insert_date_name()とほぼ一緒な感じで、set_update_date_name()ってメソッドがあるので。

「修正日」とかいうカラムがある系なら、ご利用くださいませ、的な。


あと「set_from_cgi_updateでデータを取得するときに、変更させたくないパラメタなのにパラメタインジェクションで外部から指定されちゃってショック!!」ってのが、起きないとも限らないので、的な、insertと同じお話。

set_from_cgi_updateは内部的に「set_from_cgi_detailをcallしている」だけなのと、 $this->get_all_no_key_names() で「pk以外のカラム名一式」が取得できるので、insertん時と同じように「適宜、抜くべきカラム名は抜いて」あげてくださいませ。


PK以外のデータを指定して」情報を引っ張ってきたい場合

例えばユーザデータなんかで

・IDはint

・emailがユニーク

・emailからユーザを引っ張ってきたい

なんてケース。

  // clumpインスタンス作成
  $users_c = $this->get_clump('users_clump');

  // keyを設定
  // XXX IDが空かどうかのチェックは省略
  $users_c->set_value('email', $this->get_cgi_request()->find('email'));

  // DBから取得
  $r = $hoge_c->get_nopk();
  if (false === $r) {
    // データないってよ!!
    適宜エラー処理各種
    return ;
  }

ようは、get()がget_nopk()に変わるだけ。

ただこれ「複数引っかかる場合、なにが引っかかるかは保障されない」ので、注意してね。


「一覧」とかを処理する系

MagicWeaponの基本の一つは「SQLは書いて」なのでw

その辺を前提に、一覧系を、やっぱり雑なコードで簡単に解説。


  // なんか「特定のstatus」を持ってるユーザの一覧とか検索
  $mw_sql = new mw_sql();
  $mw_sql->set_sql('SELECT * FROM users WHERE status=:status;'); // プリペアドステートメントを設定
  $mw_sql->bind(':status', $this->get_cgi_request()->find('status')); // 値をbind
  $res = $this->get_db()->query($mw_sql); // SQLの発行
  $res->set_fetch_type_hash(); // fetchのタイプをhash(カラム名)に変更

  // data_clumpのインスタンスを再利用してちょっとだけメモリ節約用:昔は結構重要だった。今はどうかなぁ?
  $users_c = null;

  // データがなくなるまでぶん回す
  $users_list = [];
  while($res->fetch()) {
    $users_c = $this->get_clump('users_clump', $users_c); // 第二引数がnullならnew、nullでなければiniti叩いてインスタンス初期化して再利用

    // データを「db_dataインスタンスから」取得
    $users_c->set_from_dbdata($res);

    // view用データを蓄積
    $users_list[] = $users_c->get_all_view_values();
  }

  // viewに設定
  $this->get_conv()->set('users_list', $users_list);

大体こんな感じ。

その他雑多で「まぁまぁ使う」子たち

del()

データの削除。単純にdeleteなんだけど、

「もし、テーブル名 + '_delete'っていう名前のテーブルが存在する」場合、そちらへのinsertを同時にやってくれる、ってあたりがちょっとだけ小細工。

テーブル名_deleteのテーブルには、テーブル名のカラム+「delete_date」ってカラム、が必須。


set_value_nowdate()

set_valueとほぼ等価なんだけど、値は「日付が自動で入る」ので、稀に便利。


set_value_token()

set_valueとほぼ等価なんだけど、値は「tokenが自動で入る」ので、稀に便利。

ちなみにtokenは、tokenizerクラス( https://github.com/gallu/MagicWeapon/blob/master/tokenizer.inc )の値。


set_value_token_with_ip()

set_value_token()とほぼ一緒なんだけど「IPアドレス付き」になるので、複数サーバでも無問題


update_calculation()

「1つのカラム」の数値を加減算できるメソッド


set_insert_id()

auto_increment時に、insertの後でこのメソッドをcallすると「IDがclumpの中に入ってくる」ので、後の取り回しが楽、かも。


あとは、data_clumpは「実はデータをmemcachedに入れられる」とか「実はデータをAPCん中に入れられる」とか、細かいギミックがいくつか。


…うん思った以上に長くなった(苦笑

まぁなんだかんだ、ある程度「MagicWeaponに特徴的なクラス」なので、細かくは色々な機能がありますw

でもまぁベースにあるのは「データを一塊で扱う」以上終了、なので。


ソースコードは、またどっかのタイミングで整理しないとねぇ(苦笑


PS

この文章は本気で「見直しをしていない」ので、突っ込みは大歓迎w

多分、定期的に修正いれますw