Introduction
When working with dates in PHP, it's important to understand the difference between mutable and immutable date objects. PHP provides two main classes for handling dates: DateTime (mutable) and DateTimeImmutable (immutable).
Similarly, if you're using the Carbon library, it also provides both mutable (Carbon\Carbon) and immutable (Carbon\CarbonImmutable) date classes.
There are some key differences between mutable and immutable date objects. And I've run into bugs more often than I'd like to admit because I didn't fully understand these differences at the time, or didn't realise which type was being used in a particular situation.
In this article, we'll explore the differences between mutable and immutable date objects in PHP and the Carbon library. We'll also look at examples of how each type behaves and explore how to use immutable date objects by default in a Laravel application.
Mutable Dates in PHP
Mutable date objects are objects which can be modified after they are created. This means that any changes made to a mutable date object will affect the original object.
For example, let's look at the following code using DateTime:
1$date = new DateTime('2025-01-01');2$date->modify('+1 day');3 4echo $date->format('Y-m-d'); // Outputs: 2025-01-02
In this example, we've created a DateTime object representing 1st January 2025. We then called the modify method to add one day. As we can see, this changed the original $date object to represent 2nd January 2025.
The Danger of Using Mutable Dates
Since the original object is updated when copied mutable dates are updated, using them can sometimes lead to unintended side effects, particularly when passing date objects around in your code.
Let's take a look at an example, and then we'll discuss what's going on. We'll create a mutable DateTime object for 2025-01-01. We'll then create another variable that references the same DateTime object and add one day to it:
1$date = new DateTime('2025-01-01');2$anotherDate = $date;3 4$anotherDate->modify('+1 day');5 6echo $date->format('Y-m-d'); // Outputs: 2025-01-027echo $anotherDate->format('Y-m-d'); // Outputs: 2025-01-02
In the code example above, we can see that adding 1 day to $anotherDate also affects $date. This is because both variables reference the same mutable DateTime object. This can lead to bugs that are hard to track down, especially in larger codebases.
I can't tell you how many times this behaviour has bitten me in the past! I've spent hours debugging issues that were caused by unintended modifications to mutable date objects.
Typically, I've found the best approach to solving this issue is to use immutable date objects instead. However, if you must use mutable dates (maybe you're working with a large codebase that would require a lot of refactoring), you might want to consider cloning the date object before making modifications instead:
1$date = new DateTime('2025-01-01');2$anotherDate = clone $date;3 4$anotherDate->modify('+1 day');5 6echo $date->format('Y-m-d'); // Outputs: 2025-01-017echo $anotherDate->format('Y-m-d'); // Outputs: 2025-01-02
As we can see in the code example, rather than using $anotherDate = $date;, we use clone to create a copy of the original date object. This way, modifications to $anotherDate do not affect $date.
Mutable Dates in Carbon
If you're using the Carbon library for date handling, the same principles apply. Here's an example using Carbon\Carbon:
1use Carbon\Carbon;2 3$date = Carbon::create(year: 2025, month: 1, day: 1);4$anotherDate = $date;5 6$anotherDate->addDay();7 8echo $date->toDateString(); // Outputs: 2025-01-029echo $anotherDate->toDateString(); // Outputs: 2025-01-02
Carbon provides a handy ->copy() method to create a clone of a mutable date object:
1use Carbon\Carbon;2 3$date = Carbon::create(year: 2025, month: 1, day: 1);4$anotherDate = $date->copy();5 6$anotherDate->addDay();7 8echo $date->toDateString(); // Outputs: 2025-01-019echo $anotherDate->toDateString(); // Outputs: 2025-01-02
Immutable Dates in PHP
Immutable date objects are date objects that cannot be modified after they are created. Any changes made to an immutable date object will result in a new object being created, leaving the original object unchanged.
In my opinion, using immutable date objects is generally the safer and more predictable approach. I've found that it helps avoid some of the pitfalls we just discussed with mutable dates. That doesn't mean mutable dates don't have their place; they can still be helpful in certain scenarios.
You can create an immutable date object using DateTimeImmutable like this:
1$date = new DateTimeImmutable('2025-01-01');2 3$newDate = $date->modify('+1 day');4 5echo $date->format('Y-m-d'); // Outputs: 2025-01-016echo $newDate->format('Y-m-d'); // Outputs: 2025-01-02
In this code example, we created a DateTimeImmutable object for 2025-01-01. When we added a day using the modify method, it returned a new DateTimeImmutable object ($newDate) without changing the original $date object. So we avoided any unintended side effects which might catch us out later.
Immutable Dates in Carbon
If you're using the Carbon library, you can use Carbon\CarbonImmutable to create immutable date objects:
1use Carbon\CarbonImmutable;2 3$date = CarbonImmutable::create(year: 2025, month: 1, day: 1);4$newDate = $date->addDay();5 6echo $date->toDateString(); // Outputs: 2025-01-017echo $newDate->toDateString(); // Outputs: 2025-01-02
Converting Mutable Dates to Immutable Dates
There may be times when you want to convert a mutable date object to an immutable one. You can do this by using the DateTimeImmutable::createFromMutable method like so:
1$mutableDate = new DateTime('2025-01-01');2 3$immutableDate = DateTimeImmutable::createFromMutable($mutableDate);4 5// Now $immutableDate is a DateTimeImmutable object
Likewise, in Carbon, you can convert a mutable Carbon\Carbon object to an immutable Carbon\CarbonImmutable object using the ->toImmutable() method:
1use Carbon\Carbon;2 3$mutableDate = Carbon::create(year: 2025, month: 1, day: 1);4$immutableDate = $mutableDate->toImmutable();5 6// Now $immutableDate is a CarbonImmutable object
Using Immutable Dates by Default in Laravel
By default, when you try to get the current date using Laravel's now() helper, it will return an instance of Illuminate\Support\Carbon, which extends the mutable Carbon\Carbon class.
However, you can change this behaviour so that now() returns an instance of Carbon\CarbonImmutable instead. This will allow you to use immutable dates by default within your Laravel application. To do this, you can use the Date::use method in your App\Providers\AppServiceProvider like so:
app/Providers/AppServiceProvider.php 1declare(strict_types=1); 2 3namespace App\Providers; 4 5use Carbon\CarbonImmutable; 6use Illuminate\Support\Facades\Date; 7use Illuminate\Support\ServiceProvider; 8 9final class AppServiceProvider extends ServiceProvider10{11 // ...12 13 /**14 * Bootstrap any application services.15 */16 public function boot(): void17 {18 Date::use(CarbonImmutable::class);19 }20}
In the code example above, we are telling Laravel to use Carbon\CarbonImmutable for all date instances created using the now() helper or any other date-related helpers provided by Laravel.
It will also ensure that any date attributes on your Eloquent models (such as created_at, updated_at) are cast to immutable date objects by default, rather than mutable ones.
Conclusion
In this article, we've explored the differences between mutable and immutable date objects in PHP and the Carbon library. We've seen how mutable date objects can lead to unintended side effects when modified, whereas immutable date objects provide a safer, more predictable approach.
If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
Or, you might want to check out my other 440+ page ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀