Laravel’s Eloquent ORM is a powerful tool for interacting with databases, and one of its standout features is the ability to customize how model attributes are accessed and modified. In Laravel 12, setters (mutators), getters (accessors), and attribute casting provide a clean and efficient way to transform data when retrieving or setting it. In this article, we’ll explore these concepts with practical examples, best practices, and clear explanations to help you leverage them in your Laravel projects.
What Are Setters, Getters, and Attribute Casting?
- Getters (Accessors): These allow you to format or transform an attribute’s value when it is retrieved from a model. For example, you might want to capitalize a name or combine multiple attributes into a single value.
- Setters (Mutators): These enable you to modify an attribute’s value before it is saved to the database. For instance, you can hash a password or convert a string to lowercase.
- Attribute Casting: This automatically converts attributes to specific data types (e.g., boolean, array, or date) when retrieved or set, without needing custom getter or setter methods.
These features help maintain clean code, ensure data consistency, and simplify complex data transformations.
Why Use Setters, Getters, and Casting?
Using these tools offers several benefits:
- Data Consistency: Ensure attributes are stored and retrieved in a consistent format.
- Code Readability: Encapsulate transformation logic within the model, reducing clutter in controllers or views.
- Security: Handle sensitive data, like encrypting values before storage.
- Flexibility: Customize attribute behavior without altering external code.
Let’s dive into a practical example to see how these features work in Laravel 12.
Example: A User Model with Setters, Getters, and Casting
Suppose we’re building an application with a User model that has attributes like first_name, last_name, is_admin, and options (a JSON field). We want to:
- Capitalize the first_name when retrieved.
- Convert the first_name to lowercase when saved.
- Combine first_name and last_name into a full_name attribute.
- Cast is_admin to a boolean and options to an array.
- Format a created_at date for display.
Here’s how we can achieve this using Laravel 12’s Eloquent features.
Step 1: Setting Up the User Model
Create a User model and migration using the Artisan command:
php artisan make:model User -m
In the migration file (database/migrations/xxxx_create_users_table.php), define the table structure:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('first_name');
$table->string('last_name');
$table->boolean('is_admin')->default(false);
$table->json('options')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
Run the migration:
php artisan migrate
Step 2: Defining the User Model
In app/Models/User.php, we’ll define the getters, setters, and casts for the User model.
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
protected $appends = ['full_name'];
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'first_name',
'last_name',
'is_admin',
'options'
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_admin' => 'boolean',
'options' => 'array',
'created_at' => 'datetime:Y-m-d',
'updated_at' => 'datetime:Y-m-d',
];
}
/**
* Get and set the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
/**
* Get the user's full name.
*/
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
}
Explanation of the Code
1. Mass Assignment:
- The $fillable property specifies which attributes can be mass-assigned (e.g., via User::create() or fill()).
2. Attribute Casting:
- The casts method specifies how model attributes should be automatically converted to and from native types.
- ‘is_admin’ => ‘boolean’: Converts the is_admin column (stored as 0 or 1 in the database) to a PHP boolean.
- ‘options’ => ‘array’: Automatically deserializes JSON data in the options column to a PHP array when retrieved and serializes it back to JSON when saved.
- ‘created_at’ => ‘datetime:Y-m-d’: Formats the created_at timestamp as a Y-m-d string (e.g., 2025–07–12) when retrieved.
3. Getter and Setter for first_name:
- The firstName() method uses the Attribute class to define both a getter and a setter:
- Getter: The get closure transforms the first_name value to have its first letter capitalized using ucfirst().
- Setter: The set closure converts the first_name to lowercase using strtolower() before saving it to the database.
4. Getter for full_name:
- The fullName() method defines a computed attribute that combines first_name and last_name. Since it’s a virtual attribute (not stored in the database), we only define a get closure.
Step 3: Using the Model in a Controller
Create a controller to test the model:
php artisan make:controller API\UserController
In app/Http/Controllers/AP/UserController.php, add methods to create and retrieve a user:
<?php
namespace App\Http\V1\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
$user = User::create([
'email' => $request->email,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'is_admin' => $request->is_admin,
'options' => $request->options,
]);
return response()->json([
'message' => 'User created',
'user' => $user,
]);
}
public function show($id)
{
$user = User::findOrFail($id);
return response()->json($user);
}
}
Define routes in routes/api.php:
use App\Http\Controllers\V1\UserController;
Route::post('/users', [UserController::class, 'store']);
Route::get('/users/{id}', [UserController::class, 'show']);
Step 4: Testing the Implementation
1. Creating a User: Send a POST request to /v1/users (e.g., using Postman or a similar tool). The input data:
{
"first_name": "JOHN",
"last_name": "DOE",
"is_admin": 1,
"options": {
"theme": "dark",
"notifications": true
}
}
The setter for first_name will convert “JOHN” to “john” before saving. The is_admin value will be stored as 1, and options will be serialized as JSON in the database.
2. Retrieving a User: Send a GET request to /api/users/1. The response will look like:
{
"id": 1,
"created_at": "2025-07-12",
"updated_at": "2025-07-12",
"is_admin": true,
"options": {
"theme": "dark",
"notifications": true
},
"first_name": "John",
"last_name": "DOE",
"full_name": "John DOE"
}
- The getter for first_name capitalizes the stored value (“john” becomes “John”).
- The full_name getter combines first_name and last_name.
- The is_admin cast converts the stored 1 to true.
- The options cast deserializes the JSON to an array.
- The created_at cast formats the timestamp as Y-m-d.
Step 5: Including Computed Attributes in JSON Output
To include the full_name attribute in the model’s JSON representation (e.g., when using toArray() or toJson()), add it to the $appends property in the User model:
protected $appends = ['full_name'];
Now, when you retrieve a user, full_name will automatically appear in the JSON output.
Best Practices for Setters, Getters, and Casting
1. Follow Naming Conventions:
- Use camelCase for accessor and mutator methods (e.g., firstName()).
- Use snake_case for database columns (e.g., first_name).
2. Keep Logic Concise:
- Avoid complex logic in getters and setters to maintain readability. If needed, move complex logic to a service class.
3. Document Behavior:
- Use PHPDoc comments to explain what each getter and setter does, especially for complex transformations.
- For straightforward type conversions (e.g., boolean, array, or date), use the casts method instead of writing custom getters and setters.
4. Avoid Overusing Accessors/Mutators:
- Only use them when you need to transform data. For simple attributes, direct access is often sufficient.
5. Be Cautious with Virtual Attributes:
- Computed attributes like full_name should be documented clearly, as they don’t correspond to database columns.
Advanced Example: Custom Casting
or more complex transformations, you can create a custom cast. Suppose you want to store a price attribute as cents (integer) in the database but work with it as dollars (float) in your application.
Use the make:cast Artisan command to create a custom cast class.
php artisan make:cast PriceCast
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsJson implements CastsAttributes
{
/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
* @return array<string, mixed>
*/
public function get(
Model $model,
string $key,
mixed $value,
array $attributes,
): array {
return $value / 100; // Convert cents to dollars
}
/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(
Model $model,
string $key,
mixed $value,
array $attributes,
): string {
return (int) ($value * 100); // Convert dollars to cents
}
}
Update the User model to use the custom cast:
use App\Casts\PriceCast;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_admin' => 'boolean',
'options' => 'array',
'created_at' => 'datetime:Y-m-d',
'updated_at' => 'datetime:Y-m-d',
'price' => PriceCast::class,
];
}
Now, if you set $user->price = 19.99, it will be stored as 1999 in the database. When retrieved, $user->price will return 19.99.
Conclusion
Laravel 12’s Eloquent setters, getters, and attribute casting provide powerful tools for customizing how model attributes are accessed and stored. By using the Attribute class for accessors and mutators, the casts method for type conversions, and custom casts for complex transformations, you can write cleaner, more maintainable code. These features help ensure data consistency, simplify your application logic, and enhance security.
Try incorporating these techniques into your next Laravel project to streamline data handling and improve code quality. Happy coding!
Free resources for this topic
Code snippets and fixes for this topic
Get one practical tip every week
New tutorials, fixes and free cheat sheets from DEV Talk, straight to your inbox. No spam, unsubscribe in one click.
Free download: Laravel interview questions (PDF)
Have a question about this post?
Ask the community: other developers and the author can answer.
Get notified about new articles
One click, no email needed. Turn it off anytime with the bell at the top.
Get new articles on your iPhone
Add DEV Talk to your Home Screen, open it, then tap the bell.
Enjoying the content?
If this article helped you, you can support the author with a coffee. It keeps new posts coming.
Comments 0
No comments yet
Be the first to share your thoughts.