Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

More Eloquent

An Eloquent model is more than access to a table. It is where everything about the data goes: common query conditions, value conversion, deletion rules. That keeps controllers short.

Query scopes

A condition repeated in several places moves into the model. The method is marked with the Scope attribute:

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class Post extends Model
{
    #[Scope]
    protected function published(Builder $query): void
    {
        $query->where('is_published', true);
    }
}
$posts = Post::published()->latest()->get();

Before Laravel 12 the method is named scopePublished and called the same way — published().

Casting types

The database returns everything as strings and numbers. The casts() method says what to turn the columns into:

class Post extends Model
{
    protected function casts(): array
    {
        return [
            'is_published' => 'boolean',
            'published_at' => 'datetime',
            'settings' => 'array',
            'price' => 'decimal:2',
        ];
    }
}

Now $post->published_at is a date object with methods, and $post->settings is an array stored in the database as JSON.

echo $post->published_at->format('d.m.Y');
echo $post->published_at->diffForHumans();

Computed properties

An accessor changes a value when it is read, a mutator when it is written:

use Illuminate\Database\Eloquent\Casts\Attribute;
 
class User extends Model
{
    protected function name(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
 
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => "{$this->first_name} {$this->last_name}",
        );
    }
}

$user->full_name works like a regular property although the table has no such column.

Soft deletes

With the SoftDeletes trait a record is not deleted but marked with a date in the deleted_at column. Regular queries no longer see it:

use Illuminate\Database\Eloquent\SoftDeletes;
 
class Post extends Model
{
    use SoftDeletes;
}

In a migration the column is created with $table->softDeletes().

$post->delete();
 
Post::withTrashed()->get();   // including deleted ones
Post::onlyTrashed()->get();   // only deleted ones
 
$post->restore();
$post->forceDelete();         // delete for good

Create or update

$user = User::firstOrCreate(
    ['email' => 'anna@example.com'],
    ['name' => 'Anna'],
);
 
$setting = Setting::updateOrCreate(
    ['key' => 'theme'],
    ['value' => 'dark'],
);

The first array is the search condition, the second holds the values for the new or updated record.

Model events

A model announces its changes. Handlers are registered in the booted() method:

use Illuminate\Support\Str;
 
class Post extends Model
{
    protected static function booted(): void
    {
        static::creating(function (Post $post) {
            $post->slug = Str::slug($post->title);
        });
    }
}

There are the events creating, created, updating, updated, deleting, deleted.

Common mistake

Events do not fire for mass queries. Post::where('user_id', 5)->delete() removes the rows with one query and does not call the deleting handlers. If you need them, delete the models one by one:

Post::where('user_id', 5)->get()->each->delete();

Thanks for reading!

Donate to the site — we work hard for you.