On vacation this week, but stopping by for the weekly Laravel release!
— Taylor Otwell 🥠(@taylorotwell) August 3, 2021
This week's 8.53.0 release includes new "immutable_date" and "immutable_datetime" casts for Eloquent! Thanks @hotmeteor 📆 ... These attributes will be cast to CarbonImmutable.https://t.co/wwSlhDWxOp
On the back of Taylor's tweet, and the contribution it referred to by Adam Campbell, I shared that in my applications, I prefer that the framework uses immutable dates by default.
What's the deal?
Consider the following example.
1$start = Carbon\Carbon::now();2$end = $start->addDay();
If you were to now look at the difference between the two dates, you might be a bit surprised at the result.
1$end->diffForHumans($start); // 1 second before
Why is this?
Carbon instances - which extend from PHP's DateTime
class - are a mutable object by default.
A mutable object is one where any modifications to that object modify or mutate the original instance of the object. If you were to further manipulate the $end
variable above, you'll find that the original $start
variable is also updated, because $end
is merely a reference to the originally instantiated object ($start
).
1$end->addHour();2 3$end->eq($start); // true
You can work around this issue in your code using the clone()
method:
1$start = Carbon\Carbon::now();2$end = $start->clone()->addDay();3 4$end->diffForHumans($start); // 1 day after
However, this is verbose and error-prone, as you still need to remember to call the clone()
method as and when needed.
This can lead to some confusing results in your code, your tests, and other obscure application bugs.
Immutability is the pathway
Fortunately, PHP - and by extension, Carbon
- provide an immutable DateTime
implementation of PHP's DateTimeInterface
.
Instead of Carbon
, you may use CarbonImmutable
.
1$start = Carbon\CarbonImmutable::now();2$end = $start->addDay();3 4$end->diffForHumans($start); // 1 day after
This is, however, still a bit tedious. Muscle memory means you're likely to use Carbon
rather than CarbonImmutable
every now and then and still land yourself in a weird place from time to time.
Laravel's Date
facade
Laravel's Date
facade - introduced in Laravel 5.8 - provides a nice gateway to using the Illuminate\Support\Carbon
class, with one added bonus: being able to configure a default date class to be used across your application.
1use Carbon\CarbonImmutable; 2use Illuminate\Support\Facades\Date; 3use Illuminate\Support\ServiceProvider; 4 5class AppServiceProvider extends ServiceProvider 6{ 7 public function register() 8 { 9 //10 }11 12 public function boot() 13 {14 Date::use(CarbonImmutable::class);15 }16}
Now whenever you use the now()
helper or the Date
facade in your code, you will always get a CarbonImmutable
instance. Likewise, if you were to use the freshTimestamp()
method on a model, or have any cast values, these will always be returned as an instance of CarbonImmutable
.
Caveat
You may well be used to using Carbon
class in your application directly, so you'll need to swap these calls to use the Date
facade in your application and use it moving forward.
The good news is that as this is a static accessor to the underlying (configured) Carbon instance, the only thing you'll need to change is your import statement (from Carbon\Carbon
or Illuminate\Support\Carbon
to Illuminate\Support\Facades\Date
) and usages (to Date
) and everything else should be good to go.