When building advanced Laravel applications, you often need to go beyond the default features. Two powerful tools for improving model behavior and query flexibility in Laravel are:
- Custom Eloquent Casts – for transforming how data is stored and retrieved on models
Query Macros – for extending the Eloquent query builder with reusable logic
What Are Custom Eloquent Casts?
They define how a model attribute should be transformed when accessed or saved. This is especially helpful for non-primitive types or custom logic.
Example: Custom Cast for JSON Settings
Let’s say you store user settings as a JSON column in the database but want to work with it as an object.
Step 1: Create a Custom Cast Class
bash
php artisan make:cast UserSettingsCast
Step 2: Implement the Cast Logic
PHP
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class UserSettingsCast implements CastsAttributes
{ public function get($model, string $key, $value, array $attributes) { return json_decode($value, true); } public function set($model, string $key, $value, array $attributes) { return json_encode($value); }
}
Step 3: Use the Cast in a Model
PHP
class User extends Model
{ protected $casts = [ 'settings' => \App\Casts\UserSettingsCast::class, ];
}
Now, $user->settings will return an array instead of a raw JSON string, and setting it works the same way.
PHP
$user->settings = ['dark_mode' => true, 'language' => 'en'];
$user->save();
What Are Query Macros?
Query Macros allow Laravel developers to extend Laravel’s query builder with custom methods, making your code DRY (Don’t Repeat Yourself) and easier to reuse across your app.
Example: Global Scope for Active Records
Let’s say you frequently filter records that have a status = active.
Step 1: Register a Macro
You can add this in a service provider, usually AppServiceProvider.
PHP
use Illuminate\Database\Query\Builder;
public function boot()
{ Builder::macro('active', function () { return $this->where('status', 'active'); });
}
If you’re using Eloquent Builder (Illuminate\Database\Eloquent\Builder), adjust accordingly.
Step 2: Use the Macro in Queries
PHP
$activeUsers = DB::table('users')->active()->get();
Or, if using Eloquent:
PHP
use Illuminate\Database\Eloquent\Builder;
Builder::macro('active', function () { return $this->where('status', 'active');
});
And then:
PHP
User::query()->active()->get();
When to Use Casts vs. Macros
Use Case | Use |
Transform model attributes | Custom Cast |
Add reusable query logic | Query Macro |
Reformat JSON, DateTime, objects | Custom Cast |
Filter by status, scope, complex joins | Query Macro |
Best Practices
- Keep custom casts simple and pure – avoid side effects.
- Use descriptive macro names for readability (->active(), ->filterByYear()).
- Place macros in dedicated service providers for organization.
Custom casts can also implement CastsInboundAttributes or Castable for even more flexibility.