Eloquent: Accessors and Mutators — Laravel Discovery Training 10

Now that we have finished the practical work part and that the basic notions are well understood, I suggest that you go a little more in depth on certain Laravel operations. We will start by going back to the Eloquent models with the scopes and the casts.

Scopes

Scopes allow you to reuse elements in Query Builders and avoid repetition. To create a scope, simply create a method starting with scope.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopePopular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }

    public function scopeOfType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}

Scopes will always have a Builder instance as the first parameter. The other parameters will be used when the method associated with the scope is used.

Then you can use the scope using methods corresponding to the name of your scopes.

$users = User::popular()->ofType('admin')->orderBy('created_at')->get();

It is also possible to define global scopes which will apply by default but they should be used with caution because it may then be difficult to remove this scope.

Soft-delete

The Soft Delete allows, when deleting a record, not to make a deletion at the database level, but simply to save its date of deletion in the database to be able to keep the information if you need to come back above. This scope can be added via a trait

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;
}

With this trait, when you delete a Model Laravel will instead update the deletion date via the field deleted_at.

If this field does not exist you can create a migration to create it:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->softDeletes();
});

Then when you fetch data from that table Laravel will always add a condition to filter records that have a deletion date. You can, if you wish, add a scope to include the deleted data:

Post::withTrashed()->get();

Cast & Mutator

Getters, setters, and casts allow you to transform attribute values ​​and Eloquent when you get or set the values.

When laravel retrieves information from the database it stores it in the property attributes of the model. Then, when we retrieve the information through a property, Laravel will use a magic method to retrieve the key that corresponds in this array of attributes.

But in addition to this recovery it is able to do some logic to transform the data to send back to us. We have an example of this situation with the creation and modification dates.

dump($post); // attributes contiendra created_at sous forme de chaine 
dump($post->created_at); // sera un objet DateTime

attribute casting

This conversion can be parameterized through a property $cast which allows you to associate a particular type with an attribute.

<?php

namespace App\Models;

use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $casts = [
        'options' => Json::class,
        'is_admin'=> 'boolean',
    ];
}

For more information on attribute casting, I refer you to the documentation.

Accessor

An accessor transforms Eloquent attribute data when accessed. To define an accessor, all you have to do is create a protected method on the model that corresponds to the name of the attribute that you want to modify (using the camelCase format).

<?php

namespace App\Models;

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

class User extends Model
{

    // Ajoute une majuscule au prénom automatiquement
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}

It is also possible to retrieve information from several attributes to design generate the value.

use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}

Note that, in this case, Laravel will automatically cache the value returned during the first access so as not to generate a new object each time the address property is accessed.

Mutator

In addition to accessors we have the possibility to define mutators in the same way thanks to another parameter in the attributes.

<?php

namespace App\Models;

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

class User extends Model
{
    /**
     * Interact with the user's first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
}

As with accessors, it is possible to modify several attributes at the same time by returning an array rather than a single return value.

use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
        set: fn (Address $value) => [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ],
    );
}