In Laravel, why does $returnBy = $placedAt->addDays(30) also move $placedAt, and how does Date::use(CarbonImmutable::class) prevent it?
answer
- Illuminate\Support\Carbon is mutable
- addDays() returns $this
- copy() or toImmutable()
- DateFactory: class, callable or factory
- type-hint CarbonInterface
basics
~10 sLaravel's default Carbon class is mutable: addDays() changes the object and returns it, so both variables hold one moved date. Date::use(CarbonImmutable::class) makes now(), today() and Eloquent dates immutable, so arithmetic returns new objects.
solid answer
~40 sBy default `now()`, `today()`, `Date::parse()` and Eloquent's date attributes create `Illuminate\Support\Carbon`, which extends the **mutable** `Carbon\Carbon`: `addDays()` changes the instance and returns `$this`. So after `$placedAt = $order->created_at; $returnBy = $placedAt->addDays(30);` both variables point at one object, and the page shows the order as placed on the return deadline. The model itself is safe — each read of `$order->created_at` builds a fresh Carbon — but any variable you reuse is not. Locally you write `->copy()->addDays(30)` or `->toImmutable()`. Globally, `Date::use(CarbonImmutable::class)` in `AppServiceProvider::boot()` makes `DateFactory` create `Carbon\CarbonImmutable` for the helpers and for Eloquent, so every `add*`/`sub*` returns a new object. The migration cost: unassigned arithmetic becomes a silent no-op, and hints on `Illuminate\Support\Carbon` must widen to `CarbonInterface`.
code
php · 15 lines<?php
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
$placed = now();
$due = $placed->addDays(14);
var_dump($placed === $due); // bool(true): same mutable object
Date::use(CarbonImmutable::class); // normally in AppServiceProvider::boot()
$placed = now();
$due = $placed->addDays(14);
var_dump($placed === $due); // bool(false): a new instance
var_dump($placed instanceof CarbonImmutable); // bool(true)go deeper
Remember that Laravel's default Carbon is mutable, so addDays() changes the variable you called it on unless you copy it first.
Explain how now(), today() and Eloquent all build dates through the Date factory, and what Date::use(CarbonImmutable::class) changes.
Diagnose wrong dates caused by one mutable object shared between variables or bindings, and plan the switch to immutable dates: unassigned calls, type hints, tests.
Decide whether immutability is an application-wide rule and how packages and teams type-hint dates so the rule holds across boundaries.
## The bug ```php <?php $placedAt = $order->created_at; $deliverBy = $placedAt->addDays(5); // intends a new date $returnBy = $placedAt->addDays(30); // intends another new date // $placedAt, $deliverBy and $returnBy are now the same object: placed + 35 days ``` `Illuminate\Support\Carbon` extends **`Carbon\Carbon`**, which is **mutable**: `addDays()` changes the instance and returns `$this`. Every variable above holds a handle to the same object, so the order page shows the order placed, delivered and returnable on one wrong date. Two details make the bug easy to misdiagnose: - **The model is not changed.** Eloquent builds a fresh Carbon from the stored value each time you read `$order->created_at`, so a second read looks correct and `save()` does not persist the shift. The damage is to your local variables. - **Query bindings share it too.** `->whereBetween('created_at', [$from, $from->addDay()])` puts the same object in both slots, so the range collapses to a single instant. ## Local fixes - `->copy()->addDays(14)` — clone first, then mutate the clone; - `->toImmutable()->addDays(14)` — convert to `CarbonImmutable`, whose methods return new instances; - avoid arithmetic on values you do not own; compute from `now()` or a fresh parse. These work, but depend on every developer remembering them on every line. ## The global switch: Date::use() Laravel creates dates through **`Illuminate\Support\DateFactory`**, exposed as the `Date` facade. The helpers `now()` and `today()` call `Date::now()` and `Date::today()`, and Eloquent's attribute casting builds dates with `Date::instance()`, `Date::parse()` and `Date::createFromTimestamp()`. `DateFactory::use($handler)` changes what all of those produce: | Handler passed to `Date::use()` | Effect | |---|---| | a class name, e.g. `CarbonImmutable::class` | dates are created as that class | | a callable | each date is created as `Illuminate\Support\Carbon`, then passed through the callable | | a `Carbon\Factory` instance | the factory creates dates, with its own settings | | anything else | `InvalidArgumentException` | `Date::useDefault()` restores the default class. ```php <?php namespace App\Providers; use Carbon\CarbonImmutable; use Illuminate\Support\Facades\Date; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Date::use(CarbonImmutable::class); } } ``` After this, `$placedAt->addDays(30)` returns a **new** `CarbonImmutable` and leaves `$placedAt` alone, and model attributes such as `created_at` come back as `CarbonImmutable` too. ## What changes when you switch 1. **Unassigned arithmetic stops working.** `$date->addDay();` on its own line used to move `$date`; with immutable dates it computes a new value and discards it. Search for such statements before switching. 2. **Type hints.** Code or packages that type-hint `Illuminate\Support\Carbon` or `Carbon\Carbon` receive a `CarbonImmutable` and throw a `TypeError`. Hint `Carbon\CarbonInterface` instead, which both implement. 3. **Tests and helpers** that relied on mutating a shared "now" need to reassign. 4. **Consistency.** Every date in the app now behaves the same way, which is the point: no more guessing whether a given instance is safe to modify. ## Per-attribute alternatives If a global switch is too big a step, the model layer can make individual attributes immutable through its cast types; that is the casts topic. `Date::use()` is the **application-wide** choice, and it also covers dates that never touch a model, such as values from `now()` in services and jobs. ## Spotting the bug in review Signs that mutation is about to bite: 1. a date stored in a variable and then used as the **base for several** calculations (`$start->addWeek()` for one value, `$start->addMonth()` for another); 2. the same date object passed twice in one expression, such as a `whereBetween` range built from `$from` and `$from->addDay()`; 3. dates held on **long-lived objects** — a singleton service, a static property, a queued job's constructor argument reused across attempts; 4. helper methods that take a date parameter and "adjust" it before using it. Each is safe with immutable dates and needs `copy()` with mutable ones. A test that asserts the original value is unchanged after a calculation catches the regression cheaply. ## When to recommend it - new applications: switch on day one, when there is no mutating code to migrate; - existing applications: switch after a search for unassigned `add*`/`sub*`/`set*` calls and widening type hints, with tests around billing and scheduling logic; - libraries and packages: do not assume either class; type-hint `CarbonInterface` and always use return values.
- After Date::use(CarbonImmutable::class) in Laravel, why might a package throw a TypeError?Dates are now `Carbon\CarbonImmutable`, which is not a subclass of `Carbon\Carbon` or `Illuminate\Support\Carbon`. Any method type-hinted on those mutable classes rejects the immutable instance. The fix is to type-hint `Carbon\CarbonInterface`, which both classes implement.
- Does Date::use() in Laravel affect Eloquent timestamps, or only the now() helper?Both. Eloquent turns stored values into dates through the `Date` factory — `Date::instance()`, `Date::parse()` and `Date::createFromTimestamp()` — so the configured class applies to `created_at`, `updated_at` and date-cast attributes as well as to `now()` and `today()`.
A mutable date is a single wall calendar with one pin: moving the pin to mark the return deadline also moves the 'order placed' marker, because there is only one pin. Immutable dates hand you a fresh printout for each marker.
saying these in an interview costs you the question
- Believes addDays() always returns a new Carbon instance in Laravel.
- Thinks Date::use() only changes the now() helper, not model dates.
- Switches to immutable dates without checking for unassigned add*/sub* calls.
- Type-hints Illuminate\Support\Carbon in shared code after going immutable.
- Builds a whereBetween range from $from and $from->addDay() with mutable dates.