0

CRUD with Eloquent — find, findOrFail, create, update, save, delete

Beginner5 min read·lv-12-004
sql

Concept

Accessors transform model attributes when you READ them. Mutators transform values when you WRITE them. Since Laravel 9, both are defined using the unified Illuminate\Database\Eloquent\Casts\Attribute class and named with camelCase — no more getFooAttribute / setFooAttribute conventions.

Modern syntax (Laravel 9+):

php
protected function firstName(): Attribute
{
    return Attribute::make(
        get: fn($value) => ucfirst($value),
        set: fn($value) => strtolower($value),
    );
}

Use Attribute::get() for read-only accessors, Attribute::set() for write-only mutators.

Legacy syntax (still works):

  • Accessor: public function getFirstNameAttribute($value) — called when accessing $model->first_name.
  • Mutator: public function setFirstNameAttribute($value) — called when assigning $model->first_name = 'Alice'.

Computed/virtual attributes: Accessors with no corresponding database column. Must be added to $appends if they should appear in toArray() / JSON serialization.

withCacheable / cached accessors: Accessors run on every access. For expensive computations, cache the result: Attribute::get(fn() => expensiveCalculation())->shouldCache().

Code Example

php
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model
{
    protected $appends = ['full_name', 'gravatar_url'];

    // Combined accessor + mutator — first_name column
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn(string $value) => ucfirst($value),
            set: fn(string $value) => strtolower($value),
        );
    }

    // Password mutator — auto-hash on assignment
    protected function password(): Attribute
    {
        return Attribute::set(fn(string $value) => bcrypt($value));
    }

    // Virtual accessor — no DB column, added to appends
    protected function fullName(): Attribute
    {
        return Attribute::get(fn() => trim("{$this->first_name} {$this->last_name}"));
    }

    // Virtual with caching (expensive operation)
    protected function gravatarUrl(): Attribute
    {
        return Attribute::get(fn() =>
            'https://gravatar.com/avatar/' . md5(strtolower($this->email))
        )->shouldCache();
    }

    // Accessor transforming serialized data
    protected function preferences(): Attribute
    {
        return Attribute::make(
            get: fn(string $value) => json_decode($value, true) ?? [],
            set: fn(array $value) => json_encode($value),
        );
    }
}

// Usage
$user = User::find(1);
$user->password = 'plaintext';   // mutator: auto-bcrypt before saving
$user->first_name = 'ALICE';     // mutator: stored as 'alice'
echo $user->first_name;          // accessor: "Alice"
echo $user->full_name;           // computed: "Alice Smith"
echo $user->gravatar_url;        // computed + cached
$prefs = $user->preferences;     // decoded array
$user->preferences = ['theme' => 'dark']; // auto-encoded

// toArray() / JSON includes $appends
$user->toArray();  // includes 'full_name' and 'gravatar_url'
$user->toJson();   // same
json_encode($user); // same