hnwの日記

PHPカンファレンス2012でphp-timecopについてLT発表しました

9月15日に開催されたPHPカンファレンス2012でLT発表してきました。以下が発表資料です。


ただ、利用シーンがイマイチわからないという声を何件か頂戴しましたので、本稿で改めて補足します。僕が想定している利用シーンは次の3つです。

  • 非エンジニアが、時刻に依存する手動テストを行う場合
    • この場合、ステージング環境で利用することになると思います
    • 例:20時からセールが始まるのを、19時時点で動作確認したい
  • エンジニアが、時刻に依存する手動テスト・自動テストを行う場合
    • 例:絶妙なタイミングで日またぎや年またぎが起こる状況を再現させたい
  • パラノイアなエンジニアが、date関数の第二引数が省略されたコードを見てブチ切れた場合


社内のニーズとしては1番目だったので伝えるには一番わかりやすいかと思ったんですが、伝わった人と伝わらない人と結構分かれてしまったようです。また、エンジニアには2番目の方が絶対伝わりやすかったですね。


3番目がどういう状況か、もう少し説明します。実は、date関数の第二引数が省略されていたら結構な確率でバグが見つかります。例えば、次のdate関数の使い方にはバグがあります。

<?php
$today_midnight = mktime(0,0,0,date("m"),date("d"),date("Y"));


date関数の第二引数を省略すると、呼び出されるたびにOSに現在時刻を問い合わせるので、たとえば上の第5引数と第6引数の呼び出しの間に年またぎが起こると、期待より1年先の時刻を取得してしまうのです。


ですから、正しく書こうと思ったら次のように書かなくてはなりません。

<?php
$currtime = time();
$today_midnight = mktime(0,0,0,date("m", $currtime),date("d", $currtime),date("Y", $currtime));


でも、ちょっと冗長すぎますし、こんなカッコ悪いコードを仕事仲間全員に書けっていうのもどうかと思うんですよね。そんなとき、php-timecopを使えば次のように書くことができます。

<?php
timecop_freeze($_SERVER['REQUEST_TIME']);
$today_midnight = mktime(0,0,0,date("m"),date("d"),date("Y"));


ここではmktimeを例に上げましたが、他のパターンでもプログラムの実行中に時間が進むのはバグの原因になりかねないと思います。


独自の日時管理クラスを作ってテストをしやすくしたり上記バグ対策をしている人も多いとは思いますが、そういったものが無い場合に利用を検討してもらえると嬉しいです。


(2012-09-18 10:00追記)もちろん、上のようにtimecop_freeze関数で時間を止めてしまうと弊害も考えられます。たとえば1秒以上かかる処理のログを出す場合など、リクエスト内で常に同じ時刻になってしまうと逆に困る状況もあるでしょう。僕自身まだ本番環境へ導入したことは無いのですが、もし本番環境に導入する場合は特に慎重な検討が必要だろうと考えています。


ちなみに、timecopを有効にしているけれども本来のtime関数を呼び出したい場合にはtimecop_orig_time関数が利用できます。time関数以外にも置き換えを行った全時刻関数についてこのようなエイリアスが定義してありますので、本番環境で利用する場合はこうした関数を一部処理で利用することになるかもしれません。