composer require cakephp/chronos:^1.2
setTestNow()
メソッドがクラスを横断して時刻セットできるようなった!
CakePHP 3.2以降、時刻操作クラスとして cakephp/chronos が採用されています。
CakePHP内ではChronosを継承した \Cake\I18n\FrozenTime
, \Cake\I18n\Time
, \Cake\I18n\FrozenDate
, \Cake\I18n\Date
を使用でき、データベースの時刻系のフィールドはこれらのクラスへマッピングされます。
また、Chronosにはテストを容易にするためにsetTestNow()
というメソッドがあり、各クラスの現在時刻を指定の時間へ固定することができます。
use \Cake\I18n\FrozenTime;
FrozenTime::setTestNow('1975-08-08 11:22:33');
$time = new FrozenTime('1 hour ago'); // 1975-08-08 12:22:33
$now = FrozenTime::now();
FrozenTime::setTestNow($now);
sleep(10); // 10秒待つ
$currentTime = FrozenTime::now(); // 固定されているので $currentTime == $now
Chronos 1.1 までの問題
しかし、Chronos 1.1 までは、setTestNow()
は各クラスごとにセットしなければなりませんでした。
以下のようにFrozenTime::setTestNow
はTime
クラスへ影響しません。
use \Cake\I18n\FrozenTime;
use \Cake\I18n\Time;
FrozenTime::setTestNow('1975-08-08 11:22:33');
$time = new FrozenTime('1 hour ago'); // 1975-08-08 12:22:33
$time = new Time('1 hour ago'); // 現在時刻の1時間後
CakePHP 3.2以降のデフォルトでは、時刻系のフィールドは FrozenTime
で処理されるようになっています。
通常は FrozenTime::setTestNow
で時刻セットを行えばよいのですが、1ヶ所だけ Time
が使われる部分があります。
TimestampBehavior
の内部では、Time
が使用されています。ですので、テスト時に FrozenTime
のみ時刻を固定した状態で created
などの TimestampBehavior
によりセットされる時刻フィールドをみると、固定した時刻ではなく実行時の時刻が入っていることになります。
これに対処するには、FrozenTime
, Time
の双方にテスト時刻をセットすればよいのですが、冗長な記述となっていました。
Chronos 1.2 ではこの問題が改善された
Chronos 1.2以降、setTestNow
での設定はすべてのChronosのサブクラスへ伝播するようになりました。
use \Cake\I18n\FrozenTime;
use \Cake\I18n\Time;
FrozenTime::setTestNow('1975-08-08 11:22:33');
$time = new FrozenTime('1 hour ago'); // 1975-08-08 12:22:33
$time = new Time('1 hour ago'); // 1975-08-08 12:22:33
これにより、各時刻クラスへのsetTestNow
の漏れがなくなり重複した記述を減らせるので、今すぐChronos 1.2以降へバージョンアップすることをお勧めします。