[12.x] Add a Custom for `AsFluent` Cast Implementation
This PR introduces a new Eloquent castable class Illuminate\Database\Eloquent\Casts\AsFluent that enables casting JSON columns into Illuminate\Support\Fluent objects (or any custom subclass of Fluent). This feature provides developers with a fluent, object-like interface for interacting with JSON attributes stored in the database, with optional support for invoking a method on the Fluent instance after hydration.
📌 Why This Is Useful
Laravel currently offers JSON casting through native array, object, or custom cast classes, but it lacks a built-in way to cast JSON directly into a Fluent object or a custom class extending Fluent. This cast implementation addresses that by:
- Allowing developers to wrap JSON attributes in a
Fluentor custom Fluent-like class. - Optionally invoking a method on the hydrated instance immediately after casting.
- Providing a clean, reusable way to handle dynamic data structures while keeping the type safety and clarity of Laravel's Eloquent casting system.
📦 Usage Example
In your Eloquent model:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\AsFluent;
class User extends Model
{
protected $casts = [
'preferences' => AsFluent::using(CustomFluent::class),
'settings' => AsFluent::using(CustomFluent::class, 'transformSettings'),
];
}
AsFluent::using(string $class, ?string $method = null): string
A convenience static method for generating the cast definition string in the format:
AsFluent::class . ':' . $class . ',' . $method;
✅ Example Custom Fluent Class
use Illuminate\Support\Fluent;
class CustomFluent extends Fluent
{
public function transformSettings()
{
// Example transformation logic
return $this->map(fn ($value) => strtoupper($value));
}
}
📋 Benefits
- Provides a native, reusable cast class for JSON-to-Fluent mapping.
- Keeps Laravel's casting ecosystem consistent and flexible.
- Simplifies working with dynamic data structures stored as JSON.
- Adds optional transformation step via method invocation.
Your description might be off. It looks like you are adding AsFluent case, which has already been added in #56046. However, what your code actually does, is adding a using() method to the existing class. Maybe you could make this more clear—that would be great 😉
Thanks for your pull request to Laravel!
Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.
If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!
May I offer a quick suggestion: maybe, add another caster, named like AsAlwaysFluent::class?
The difference is that the attribute would always be Fluent instance, even if the database has null in this field.
To get rid of constant checking if the field is not null…
// before:
$err = $file->report?->get('uploaded.error');
// after:
$err = $file->report->get('uploaded.error');