Mutadors i Accessors
Com personalitzar atributs Eloquent: accessors, mutadors, attribute casting i casts personalitzats.
Què són els accessors i mutadors?#
Els accessors i mutadors permeten transformar els valors dels atributs d'Eloquent quan els llegeixes o els escrius. Un accessor modifica el valor quan accedeixes a l'atribut (lectura), i un mutador modifica el valor quan l'assignes (escriptura). Això permet que les dades es guardin a la base de dades en un format i es presentin a l'aplicació en un altre.
Definir accessors#
Per definir un accessor, crea un mètode protegit al model amb el nom de l'atribut que retorna una instància de Attribute. El paràmetre get del constructor rep el valor original i retorna el valor transformat:
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
}Ara, cada cop que accedeixes a $user->name, el valor es retorna amb la primera lletra en majúscula:
$user = User::find(1);
echo $user->name; // "joan" a la BD → "Joan" a l'aplicacióPots definir accessors per a atributs que no existeixen a la base de dades. Això és útil per crear atributs virtuals calculats a partir d'altres camps:
class User extends Model
{
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) =>
$attributes['first_name'] . ' ' . $attributes['last_name'],
);
}
}
echo $user->full_name; // "Joan Garcia"El segon paràmetre del closure ($attributes) conté tots els atributs del model, cosa que permet calcular valors basant-se en múltiples camps.
Caching d'accessors#
Per defecte, Eloquent no guarda en cache el resultat dels accessors. Si l'accessor fa una operació costosa, pots activar el caching amb shouldCache():
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) =>
$attributes['first_name'] . ' ' . $attributes['last_name'],
)->shouldCache();
}Definir mutadors#
Un mutador transforma el valor quan l'assignes a l'atribut. Es defineix amb el paràmetre set de Attribute:
class User extends Model
{
protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
}Ara, quan assignes un valor a name, es guarda en minúscules a la base de dades:
$user->name = 'JOAN GARCIA';
// Es guarda com "joan garcia" a la BD
echo $user->name; // Es mostra com "Joan garcia"Un mutador pot modificar múltiples columnes a la vegada retornant un array:
class User extends Model
{
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) =>
$attributes['first_name'] . ' ' . $attributes['last_name'],
set: function (string $value) {
$parts = explode(' ', $value, 2);
return [
'first_name' => $parts[0],
'last_name' => $parts[1] ?? '',
];
},
);
}
}
$user->full_name = 'Joan Garcia';
// Guarda first_name = 'Joan' i last_name = 'Garcia'Un cas pràctic és el hashing de contrasenyes:
class User extends Model
{
protected function password(): Attribute
{
return Attribute::make(
set: fn (string $value) => bcrypt($value),
);
}
}
$user->password = 'secret'; // Es guarda hashejat automàticamentAttribute Casting#
El casting d'atributs converteix automàticament els tipus de dades quan llegeixes o escrius atributs. A diferència dels accessors i mutadors, els casts són declaratius: defins el tipus i Laravel s'encarrega de la conversió. Defineix els casts a la propietat $casts del model o al mètode casts():
class Article extends Model
{
protected function casts(): array
{
return [
'published' => 'boolean',
'published_at' => 'datetime',
'options' => 'array',
'price' => 'decimal:2',
'metadata' => 'collection',
'created_at' => 'datetime:Y-m-d',
];
}
}Alternativament, pots utilitzar la propietat $casts:
protected $casts = [
'published' => 'boolean',
'published_at' => 'datetime',
'options' => 'array',
];Tipus de casts disponibles#
Laravel inclou casts per als tipus més comuns:
// Booleans: converten 0/1 a true/false
$article->published; // true (no "1")
// Dates: retornen instàncies de Carbon
$article->published_at; // Carbon instance
$article->published_at->format('d/m/Y'); // "15/03/2024"
$article->published_at->diffForHumans(); // "fa 2 dies"
// Arrays: converteix JSON de la BD a array PHP
$article->options; // ['color' => 'blue', 'size' => 'large']
$article->options = ['color' => 'red'];
$article->save(); // Es guarda com JSON a la BD
// Col·leccions: com array però retorna una Collection
$article->metadata->get('key');
$article->metadata->has('key');
// Decimals: formata amb el nombre de decimals especificat
$article->price; // 29.99 (no 29.990000000001)
// Integers i floats
'votes' => 'integer',
'rating' => 'float',
// Dates amb format personalitzat
'birthday' => 'date:Y-m-d',
'published_at' => 'datetime:Y-m-d H:i',
// Enums (PHP 8.1+)
'status' => ArticleStatus::class,
// Objectes immutables
'published_at' => 'immutable_datetime',
'options' => AsCollection::class,Cast d'Enums#
A partir de PHP 8.1, pots fer cast d'atributs directament a enums:
enum ArticleStatus: string
{
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
class Article extends Model
{
protected function casts(): array
{
return [
'status' => ArticleStatus::class,
];
}
}
$article->status; // ArticleStatus::Published
$article->status = ArticleStatus::Draft;
$article->save();Cast d'arrays i JSON#
El cast array converteix una columna JSON de la base de dades a un array PHP, i viceversa:
class Product extends Model
{
protected $casts = [
'options' => 'array',
];
}
$product = Product::find(1);
$options = $product->options; // Array PHP
$options['color'] = 'red';
$product->options = $options;
$product->save(); // Es guarda com JSON
// Amb AsArrayObject pots modificar directament sense reassignar
protected $casts = [
'options' => AsArrayObject::class,
];
$product->options['color'] = 'red'; // Funciona directament
$product->save();Casts personalitzats#
Quan els casts integrats no cobreixen les teves necessitats, pots crear casts personalitzats. Un cast personalitzat és una classe que implementa la interfície CastsAttributes:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class Address implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject
{
return new AddressValueObject(
json_decode($value, true)
);
}
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return json_encode([
'street' => $value->street,
'city' => $value->city,
'zip' => $value->zip,
]);
}
}Per utilitzar-lo al model:
class User extends Model
{
protected function casts(): array
{
return [
'address' => Address::class,
];
}
}
$user->address; // AddressValueObject
$user->address->city; // "Andorra la Vella"Casts amb paràmetres#
Pots crear casts que acceptin paràmetres implementant el constructor:
class CurrencyFormat implements CastsAttributes
{
public function __construct(
protected string $currency = 'EUR',
protected int $decimals = 2,
) {}
public function get(Model $model, string $key, mixed $value, array $attributes): string
{
return number_format($value / 100, $this->decimals) . ' ' . $this->currency;
}
public function set(Model $model, string $key, mixed $value, array $attributes): int
{
return (int) ($value * 100);
}
}protected function casts(): array
{
return [
'price' => CurrencyFormat::class . ':EUR,2',
];
}Casts d'entrada o sortida#
Si el cast només necessita transformar el valor en una direcció, pots implementar CastsInboundAttributes (només per escriptura):
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
class Hash implements CastsInboundAttributes
{
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return bcrypt($value);
}
}Això és útil per a casos com el hashing de contrasenyes, on transformes el valor en guardar-lo però no necessites cap transformació en llegir-lo.