PHPの日時をコントロールするDateTimeクラスはよく見ます。同じようなクラスにDateTimeImmutableもあります。
使い方が同じなのでどうでもいいと思われがちですが、ちがいをきちんと理解して使い分けましょうという話です。
というか絶対に使い分けてほしい!
DateTimeとDateTimeImmutableのちがい
DateTimeとDateTimeImmutableのちがいは、
最初に設定した日時を残すか?
これだけです。
DateTime | Mutable (変更する) 元データ消える |
DateTimeImmutable | Immutable (変更しない) 元データ残す |
クラス名そのままですね?DateImmutableはDateTimeの元データを変更できない用に用意されたクラスです。
メソッドもほぼ同じでそのパラメータも同じです。使い方は同じと考えていいです。
ふたつのクラスの違いはcreateFrom***()です。
DateTime | createFromImmutable() DateTimeImmutableからDateTimeを作る。 ※ PHP7.3以上 |
DateTimeImmutable | createFromMutable() DateTimeからDateTimeImmutableを作る。 ※ PHP5.6以上 |
ちがいが分かるサンプルコード
ふたつのオブジェクトをメソッドで変更するサンプルコードを作りました。一気に実行します。
function output($t1, $t2, $t1_chg, $t2_chg, $title) {
$format = 'Y-m-d D H:i:s.v e';
echo '----- ' . $title . ' -----' . PHP_EOL;
echo 'DateTime org : ' . $t1->format($format) . PHP_EOL;
if( $t1_chg ) echo 'DateTime chg : ' . $t1_chg->format($format) . PHP_EOL;
echo 'DateTimeImmutable org: ' . $t2->format($format) . PHP_EOL;
if( $t2_chg ) echo 'DateTimeImmutable chg: ' . $t2_chg->format($format) . PHP_EOL;
echo PHP_EOL;
}
$time = '2010-01-01 01:01:01.111';
$t1 = new DateTime($time);
$t2 = new DateTimeImmutable($time);
output($t1, $t2, null, null, 'new constructor');
$year = 2011;
$month = 2;
$day = 2;
$t1_chg = $t1->setDate($year, $month, $day);
$t2_chg = $t2->setDate($year, $month, $day);
output($t1, $t2, $t1_chg, $t2_chg, 'setDate');
$hour = 2;
$minute = 2;
$second = 2;
$t1_chg = $t1->setTime($hour, $minute, $second);
$t2_chg = $t2->setTime($hour, $minute, $second);
output($t1, $t2, $t1_chg, $t2_chg, 'setTime');
$timestamp = strtotime('2012-03-03 03:03:03.333');
$t1_chg = $t1->setTimestamp($timestamp);
$t2_chg = $t2->setTimestamp($timestamp);
output($t1, $t2, $t1_chg, $t2_chg, 'setTimestamp');
$timezone = new DateTimeZone('Asia/Tokyo');
$t1_chg = $t1->setTimezone($timezone);
$t2_chg = $t2->setTimezone($timezone);
output($t1, $t2, $t1_chg, $t2_chg, 'setTimezone');
$modify = '+1 year +1 week';
$t1_chg = $t1->modify($modify);
$t2_chg = $t2->modify($modify);
output($t1, $t2, $t1_chg, $t2_chg, 'modify');
$interval = DateInterval::createFromDateString('2 weeks');
$t1_chg = $t1->add($interval);
$t2_chg = $t2->add($interval);
output($t1, $t2, $t1_chg, $t2_chg, 'add');
$interval = DateInterval::createFromDateString('3 weeks');
$t1_chg = $t1->sub($interval);
$t2_chg = $t2->sub($interval);
output($t1, $t2, $t1_chg, $t2_chg, 'sub');
----- new constructor -----
DateTime org : 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
----- setDate -----
DateTime org : 2011-02-02 Wed 01:01:01.111 UTC
DateTime chg : 2011-02-02 Wed 01:01:01.111 UTC
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2011-02-02 Wed 01:01:01.111 UTC
----- setTime -----
DateTime org : 2011-02-02 Wed 02:02:02.000 UTC
DateTime chg : 2011-02-02 Wed 02:02:02.000 UTC
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2010-01-01 Fri 02:02:02.000 UTC
----- setTimestamp -----
DateTime org : 2012-03-03 Sat 03:03:03.000 UTC
DateTime chg : 2012-03-03 Sat 03:03:03.000 UTC
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2012-03-03 Sat 03:03:03.000 UTC
----- setTimezone -----
DateTime org : 2012-03-03 Sat 12:03:03.000 Asia/Tokyo
DateTime chg : 2012-03-03 Sat 12:03:03.000 Asia/Tokyo
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2010-01-01 Fri 10:01:01.111 Asia/Tokyo
----- modify -----
DateTime org : 2013-03-10 Sun 12:03:03.000 Asia/Tokyo
DateTime chg : 2013-03-10 Sun 12:03:03.000 Asia/Tokyo
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2011-01-08 Sat 01:01:01.111 UTC
----- add -----
DateTime org : 2013-03-24 Sun 12:03:03.000 Asia/Tokyo
DateTime chg : 2013-03-24 Sun 12:03:03.000 Asia/Tokyo
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2010-01-15 Fri 01:01:01.111 UTC
----- sub -----
DateTime org : 2013-03-03 Sun 12:03:03.000 Asia/Tokyo
DateTime chg : 2013-03-03 Sun 12:03:03.000 Asia/Tokyo
DateTimeImmutable org: 2010-01-01 Fri 01:01:01.111 UTC
DateTimeImmutable chg: 2009-12-11 Fri 01:01:01.111 UTC
DateTimeは、コンストラクタで指定した日時(元データ)がメソッドで変更するたびに更新されます。
しかしDateTimeImmutableは、コンストラクタで指定した日時はメソッドを実行しても変わりません。
かといって、DaeTimeImmutableで変更できないことはありません。できます。
DateTime系クラスは、メソッドのリターンで自身と同じクラスのオブジェクトを返します。DateTimeImmutableは元のオブジェクトはそのままに、変更したオブジェクトを新しく作って返します。
DateImmutableの日時を変更したければ、メソッドの結果のオブジェクトを使えばいいです。
一方、DateTimeは元のオブジェクトを変更してそれをメソッドの結果で返します。
(元のオブジェクトと戻り値のオブジェクトは同じなので、メソッドの戻り値を使う意味がない。)
基本DateTimeImmutableを使う
DateTimeはデータを更新しつづけることができます。でもバグになりやすいです。
サンプルコードで見てみましょう。
function output($t1, $t1_chg, $title) {
$format = 'Y-m-d D H:i:s.v e';
echo '----- ' . $title . ' -----' . PHP_EOL;
echo 'DateTime org : ' . $t1->format($format) . PHP_EOL;
if( $t1_chg ) echo 'DateTime chg : ' . $t1_chg->format($format) . PHP_EOL;
echo PHP_EOL;
}
$time = '2010-01-01 01:01:01.111';
$t1 = new DateTime($time);
function change($t1) {
$year = 2011;
$month = 2;
$day = 2;
$t1_chg = $t1->setDate($year, $month, $day);
output($t1, $t1_chg, 'function change');
}
change($t1);
class Sample {
private $obj;
function __construct($obj){
$this->obj = $obj;
}
public function update() {
$tmp = $this->obj;
$year = 2012;
$month = 3;
$day = 3;
$tmp_chg = $tmp->setDate($year, $month, $day);
output($this->obj, $tmp_chg, 'Sample Object update');
return $this->obj;
}
}
$sample = new Sample($t1);
$sample->update();
output($t1, null, 'use continue');
----- function change -----
DateTime org : 2011-02-02 Wed 01:01:01.111 UTC
DateTime chg : 2011-02-02 Wed 01:01:01.111 UTC
----- Sample Object update -----
DateTime org : 2012-03-03 Sat 01:01:01.111 UTC
DateTime chg : 2012-03-03 Sat 01:01:01.111 UTC
----- use continue -----
DateTime org : 2012-03-03 Sat 01:01:01.111 UTC
関数やクラスの中でDateTimeオブジェクトを変更すると、その内容が元のオブジェクトに反映されます。
オブジェクト指向はとくに、クラスの集合体でできてるので、どこで、どのように変更されているか分かりづらいです。
また、データベースから取得したデータを知らぬところで変更されては困るでしょう。
データを変更するときは、はっきりと分かるようにする。
これは基本です。これに最適なのがDateTimeImmutable。メソッドで変更しても元データは残りますから。
さっきのサンプルをDateTimeImmutableに変えて実行します。
$time = '2010-01-01 01:01:01.111';
//$t1 = new DateTime($time);
$t1 = new DateTimeImmutable($time); // <--- ここだけ変更
----- function change -----
DateTime org : 2010-01-01 Fri 01:01:01.111 UTC
DateTime chg : 2011-02-02 Wed 01:01:01.111 UTC <--- 関数内で加工して使う
----- Sample Object update -----
DateTime org : 2010-01-01 Fri 01:01:01.111 UTC
DateTime chg : 2012-03-03 Sat 01:01:01.111 UTC <--- オブジェクト内で加工して使う
----- use continue -----
DateTime org : 2010-01-01 Fri 01:01:01.111 UTC <--- 関数・オブジェクトでどう使われようが元データは変わらない。
プログラマはクラス・関数名で処理を想像する
DateTimeImmutableを使うようにすすめる最大の理由は、プログラマの性質です。
プログラマの仕事は大量のコードを読みます。作業をするとき一言一句読むなんてことはしません。
クラス名・メソッド名・関数名で何をしているのか当たりをつけて関係なさそうならスルーします。
そのとき、DateTimeオブジェクトを見たら
と直感で思います。
(逆にDateTimeImmutableを見ると『変わらないデータだな』と思う。)
それなのに変更がなかったとしたら... 時間のムダです。
日時オブジェクトはDateTimeImmutableを使う。
変更するときだけDateTimeにして変更する。
DateTimeImmutableオブジェクトを上書き。
としてくれたほうが余計な時間を使いません。
他人が読みやすいプログラムを書くのもプログラマの仕事です。
(プログラマの作業時間も利益に直結している。)
一人で最後まで責任をもてる仕事ならいいですが、そんな仕事ないですからね? ほぼ。
コードはこんな感じになります。
function output($t1, $t1_chg, $title) {
$format = 'Y-m-d D H:i:s.v e';
echo '----- ' . $title . ' -----' . PHP_EOL;
echo 'DateTimeImmutable org : ' . $t1->format($format) . PHP_EOL;
if( $t1_chg ) echo 'DateTimeImmutable chg : ' . $t1_chg->format($format) . PHP_EOL;
echo PHP_EOL;
}
$time = '2010-01-01 01:01:01.111';
$t1 = new DateTimeImmutable($time);
output($t1, null, 'create');
class Sample {
private $obj;
function __construct($obj){
$this->obj = $obj;
}
/**
* DateTimeImmutableのメソッドだけで変更する
*/
public function update1() {
$tmp = $this->obj;
$year = 2012;
$month = 3;
$day = 3;
// 変更したオブジェクトで上書き
$this->obj = $tmp->setDate($year, $month, $day);
return $this->obj;
}
/**
* 変更するときDateTimeを使う
*/
public function update2() {
$tmp = DateTime::createFromImmutable($this->obj);
$year = 2013;
$month = 4;
$day = 4;
$tmp->setDate($year, $month, $day);
// 変更したオブジェクトで上書き
$this->obj = DateTimeImmutable::createFromMutable($tmp);
return $this->obj;
}
}
$sample = new Sample($t1);
// オブジェクト更新
$t1 = $sample->update1();
output($t1, null, 'update1');
// オブジェクト更新
$t1 = $sample->update2();
output($t1, null, 'update2');
----- create -----
DateTimeImmutable org : 2010-01-01 Fri 01:01:01.111 UTC
----- update1 -----
DateTimeImmutable org : 2012-03-03 Sat 01:01:01.111 UTC
----- update2 -----
DateTimeImmutable org : 2013-04-04 Thu 01:01:01.111 UTC
これで、『どこで』『どのように』日時が更新されたか分かると思います。
データの更新は、はっきり分かるようにしないとバグの温床になる。