Michael Dyrynda
Home Blog Podcasts
 
Immutable dates in Laravel August 4th, 2021

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.

I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight