ごあいさつ
こんにちは!開発本部カードバトル推進委員会長石川です。
MTGでは遂にテーロスが発売されました!テーマは厨二病患者万歳の「ギリシャ神話」!
我々はついに神を召還することができるのです!!心躍りますね!
期待の新星PHP5.5
今年の6月、PHP5.5が正式にリリースされました。
2月にこの会社に入って初めてPHPを触った僕にとっては初のメジャーアップデートです。
既にリリースから4ヶ月以上たってますが、新機能を実際にコードを動かしながら確認していこうと思います。
マニュアルはこちらです。
ジェネレータの追加
ジェネレータを使えば、シンプルな イテレータを簡単に実装できます。 Iterator インターフェイスを実装したクラスを用意する オーバーヘッドや複雑さを心配する必要はありません。
早速書いてみる
1 2 3 4 5 6 7 8 9 10 |
function tenCountGenerator() { for ($i = 1; $i <= 10; $i++) { yield $i; // 値を返却する } } $gen = tenCountGenerator(); // ジェネレータオブジェクトの生成 foreach ($gen as $count) { // next処理 echo $count; }; |
処理結果
12345678910
こんな感じですね。
foreachの部分がいわゆるnext処理で、ジェネレータ関数内のyieldに到達するまで処理します。
で、次のyieldが見つからなくなると処理が終わります。
で、どこで使うわけ?
マニュアルにもある通り、メモリ内で巨大な配列を組み立てる可能性のある箇所なんかに適用するとメモリを節約できます。
たとえばこんな関数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function getLines($filename) { $result = array(); $f = fopen($filename, 'r'); // ファイル開く $line = fgets($f); // 1行読む while ($line !== false) { $result[] = $line; // 一行の内容を配列にぶちこむ $line = fgets($f); // 1行読む } fclose($f); return $result; } $result = getLines('test.txt'); foreach($result as $line) { echo $line; } |
ファイルを開く→行毎の配列を作る→ファイルを閉じる→配列を返却する
かなり突っ込みどころ満載ではありますが、こんな流れで動くメソッドがあるとします。
渡すファイルが小さければ問題ありませんが、これが巨大なデータになった時にはアウトオブメモリーになる可能性があります。
ジェネレータを使うとこんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function readLineGenerator($filename) { $f = fopen($filename, 'r'); // ファイル開く $line = fgets($f); // 1行読む while ($line !== false) { yield $line; // 一行毎に返却する $line = fgets($f); // 1行読む } fclose($f); } $gen = readLineGenerator('test.txt'); foreach($gen as $line) { echo $line; } |
処理結果
【速報】「きのこの山」が生産中止!10月下旬には早くも販売停止
この度きのこの山の売り上げ低迷により販売停止になることが正式に発表された。
きのこの山とたけのこの里の争いは古くは弥生時代に始まったと言われており、
この後きのこの山ファンはどうやって生きていくのか、注目が集まっている。
配列を使う形式では、ファイル内容を全てメモリに展開するのに対し、
ジェネレーターを使う形式では1行ごとにメモリ展開、破棄をする為、
メモリオーバーの危険性が下がりました。
共通化視点
行の中身を全て必要とするのか、空白行をスキップしたいのか、カンマ区切りにするのか。
読み込んだ行をどう調理するかは呼び出し側が決めることが出来ます。
ジェネレーターを利用することで、この様にループの絡んだ汎用的な処理をメソッド化出来るので、
共通化の観点から考えてもGoodですね。
まとめ
今回のサンプルの様に、ループの外側に汎用性の高い処理がある場合に、
メモリを節約しつつループの内側の処理を追い出す事が出来るので、
色々なところに適用できそうで良いですね。
finallyキーワードの追加
try-catch ブロックで finally が使えるようになりました。例外が発生したかどうかに関わらず 必ず実行しないといけないコードをここに書きます。
もともと僕はjava使いなので、try-catch-finallyは3本でセットだと思ってたのですが、
PHPにはfinallyが無いことを知った時は驚きました。
finallyさんさえ居てくれれば・・・そんなシーンも少なくはありませんでした。
そんなもにょもにょした感情も、これでお別れです。
とりあえず書いてみる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function finalFlash($セルが邪魔する) { $result = 'ファイナル・・・'; try { if ($セルが邪魔する) throw new Exception('ぬわーッ!!'); $result .= 'フラーッシュ!!'; return $result; } catch(Exception $e) { $result .= $e->getMessage(); return $result; } finally { $result .= "\n" . 'Doooooon!!!'; // 例外の発生した、しないに関わらず必ず実行される } } echo finalFlash(true); echo "\n"; echo finalFlash(false); |
処理結果
ファイナル・・・ぬわーッ!!
Doooooon!!!
ファイナル・・・フラーッシュ!!
Doooooon!!!
セルが邪魔すればベジータのファイナルフラッシュが失敗します。
成功しても失敗しても「Doooooon!!!」は出力されているので、
returnの直前にfinally処理が実行されていることがわかります。
まぁセルの性格から言って、ファイナルフラッシュを途中で邪魔することなどまず無く、
恐らく全力でベジータにファイナルフラッシュを打たせて、
全身で受け止めて無傷 or 一喝するだけでかき消す
のどちらかでベジータの気力を奪うでしょうが。
これだけじゃあんまりなので
先ほどのGeneratorと組み合わせてみることにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function readLineGenerator($filename) { $f = fopen($filename, 'r'); $line = fgets($f); while ($line !== false) { yield $line; $line = fgets($f); } fclose($f); // ここが呼ばれない echo '確かに閉じたぜ!'; } $gen = readLineGenerator('test.txt'); foreach($gen as $line) { echo $line; break; // 強制的に終了させる } |
処理結果
【速報】「きのこの山」が生産中止!10月下旬には早くも販売停止
実は上で書いたメソッドにはバグがありました。
ジェネレーターの処理を途中で強制終了させる場合に、fcloseが呼ばれないのです。
(ファイル開くのに失敗した場合のハンドリングも無いですがこのケースでは無視します)
そこでfinallyを使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function readLineGenerator($filename) { $f = fopen($filename, 'r'); try { $line = fgets($f); while ($line !== false) { yield $line; // 一行毎に返却する $line = fgets($f); } } finally { fclose($f); echo '確かに閉じたぜ!'; } } $gen = readLineGenerator('test.txt'); foreach($gen as $line) { echo $line; break; // 強制的に終了させる } |
処理結果
【速報】「きのこの山」が生産中止!10月下旬には早くも販売停止
確かに閉じたぜ!
finally処理を行っていることが確認できます。
ジェネレーターと組み合わせた場合、ジェネレーターオブジェクトが参照されなくなった際に自動的にfinally処理が実行される様になっています。
これは嬉しいですね。このタイミングでfinallyが実装されたのもうなずける連携プレイです。
逆に考えると、ジェネレーター内部に今回の様な終了処理がある場合は気をつけなければいけません。
ジェネレーターの利用する場合finally処理が必要かどうか意識する、というのは重要なポイントですね。
新しいパスワードハッシュ API
新しいパスワードハッシュ API が用意され、より簡単にパスワードをハッシュしたり管理したりできるようになりました。
早速書いてみる
1 2 3 4 5 6 |
$password = 'GoemonLets5'; // パスワード $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 11]); // パスワード、アルゴリズム、計算コストを設定 echo $hash; echo "\n"; var_dump(password_get_info($hash)); |
処理結果
$2y$11$UMY6ntr8oYl71v.i0UMjOOK17C/Byuz8YkAlA5La1fzaNX2VVxVMa
array(3) {
‘algo’ => int(1)
‘algoName’ => string(6) “bcrypt”
‘options’ => array(1) { ‘cost’ => int(11) }
}
password_hashでハッシュ化を行います。
password_get_infoでハッシュ情報が取得できます。
そのままの流れで照合していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// パスワードは'GoemonLets5'です // $hashにはパスワードのhash値が入ってます if (password_verify('GoemonLets5shinai', $hash)) { echo 'パスワードが一致したよ!' . "\n"; } else { echo 'パスワードが一致してないよ!' . "\n"; } if (password_verify('GoemonLets5', $hash)) { echo 'パスワードが一致したよ!' . "\n"; } else { echo 'パスワードが一致してないよ!' . "\n"; } |
処理結果
パスワードが一致してないよ!
パスワードが一致したよ!
はい、こんな感じで照合できました。
かなり簡単にパスワードのハッシュ化、照合が行えますね!
で、新APIのなかに気になるメソッドがひとつ。
boolean password_needs_rehash ( string $hash , string $algo [, string $options ] )
– 指定したハッシュがオプションにマッチするかどうかを調べる
説明だけを見ると何のためにあるのかよくわからなかったんですが、
password_needs_rehashなので、パスワードを再ハッシュする必要があるかどうかを
判定する為に用意されているみたいですね。
早速動かしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function rehashPassword($password, $hash) { if (password_needs_rehash($hash, PASSWORD_BCRYPT,['cost' => 12])) { // 計算コスト12で再ハッシュ化の必要があるかチェック echo 'パスワードを再ハッシュするよ!' . "\n"; $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]); // 計算コスト12で再度ハッシュ化する // DBに保存しなおしとかそういう処理 } else { echo 'パスワードの再ハッシュは不要だよ!' . "\n"; } return $hash; } $password = 'GoemonLets5'; // パスワード $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 11]); // 計算コスト11でハッシュ化する $hash = rehashPassword($password, $hash); // コスト11で暗号化されたハッシュに対してコスト12で暗合されているかチェックをかける $hash = rehashPassword($password, $hash); // 直前の処理でハッシュはコスト12に再計算されているので、再ハッシュは不要になる |
処理結果
パスワードを再ハッシュするよ!
パスワードの再ハッシュは不要だよ!
こんな感じですね。
1度ハッシュ化したが、例えば後でアルゴリズムが変わった時や計算コストがあがった時など、
一度設定したパスワードのハッシュを更新したい時に使うようです。
ということで、PHP5.5の新機能について色々試してみました。
ジェネレータが良さげですね。煩雑だった処理がスッキリしそうな気がします。
続きはコチラ。
ここまでお読み頂き、ありがとうございました!m(_ _)m
参考リンク
今回ブログを書くにあたって参考にさせて頂きました。感謝です!
PHP5.5新機能「ジェネレータ」初心者入門
さっくり理解するPHP 5.5の言語仕様と「いい感じ」の使い方
アライドアーキテクツでVPoPをしています。おもにダイエットに関する話を書きます。たまにサービス開発において大事だと思っていることを書いたりします。