Carbon処理の意外な落とし穴
普段何気なくCarbonを使用していないでしょうか?私もハマったのですが、Carbon処理には以下のような落とし穴が存在します
<?php
use Carbon\Carbon;
$now = Carbon::now();
$tomorrow = $now->addDay();
if ($now->format('Y/m/d H:i:s') !== $tomorrow->format('Y/m/d H:i:s')}) {
echo "期待する結果です!";
} else {
echo "意図しない結果です"
}
このコードは、$now
と$tommorow
は別々の日付を代入しているので、期待する結果です!
という値が表示されるはずです
実行結果:
意図しない結果です
なんということでしょう・・・。$now
と$tommorrow
が等しいと評価されています。これは、「Carbonのメソッドを呼び出したインスタンス自身が書き換えられる」ことが原因です
つまり、下記コード(一部抜粋)の$now->addDay()
でインスタンスがnow
ではなくtomorrow
に書き換えられえています
$now = Carbon::now();
$tomorrow = $now->addDay(); // インスタンス自身が書き換えられる
この仕様により、バグを引き起こす原因になり得ます・・・。Carbon::copy()
で状態の変化を防ぐことも可能ですが、毎回この処理を書くのは大変ですし、変更漏れも起こり得ます
そこで登場するのが、CarbonImmutable
です
※ mutableは「変更可能な」、immutableは「変更不可能な、不変の」という意味を持つ形容詞である
CarbonImmutableを使用する
こちらはCarbonのイミュータブル(不変)版です。組み込みのDateTimeImmutableクラスを継承しています
こちらは新しいインスタンスが返されるため、copy()する必要がありません。さらにインスタンスが作られた一番最初の状態を持つので、「思わぬ副作用」が発生しないのです
先ほどのサンプルコードをCarbonImmutableで書き換えてみましょう
<?php
use Carbon\CarbonImmutable;
$now = CarbonImmutable::now();
$tomorrow = $now->addDay(); // Immutableなので新しいインスタンスが返却される
if ($now->format('Y/m/d H:i:s') !== $tomorrow->format('Y/m/d H:i:s')}) {
echo "期待する結果です!";
} else {
echo "意図しない結果です"
}
実行結果:
期待する結果です!
これで期待する結果になりましたね。何か特別な理由がない限りはLaravelでは思わぬ副作用を防ぐためにも、CarbonImmutableを使った方が無難かなあと思います
補足:イミュータブル(immutable)とは
イミュータブル(immutable)なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。対義語はミュータブル(mutable)なオブジェクトで、作成後も状態を変えることができる。
おわりに
というわけで、この記事ではLaravelで日付操作をする場合は、Carbonではなく「CarbonImmutable」の使用について紹介してきました
CarbonImmutableは組み込みのDateTimeImmutableクラスを継承しているので、気になる方は下記リンクからご覧ください(見なくてもいいかもですが)
https://www.php.net/manual/ja/class.datetimeimmutable.php