Capítulo 06: Modelos Eloquent, Mass Assignment, Mutators e Casts
Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Dados brutos do banco"] --> B["Eloquent Model"]
B --> C["casts(): tipos nativos PHP"]
B --> D["$fillable: whitelist de Mass Assignment"]
B --> E["Attribute::make(): accessors/mutators"]
B --> F["Local Scopes: filtros reutilizáveis"]
C --> G["Objeto PHP tipado e seguro"]
D --> G
E --> G
F --> G
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style G fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. Mass Assignment e a Whitelist $fillable
Quando um array de dados é passado diretamente para Model::create($dados) ou $model->update($dados), o Eloquent tentaria atribuir todos os campos do array às colunas do model — se o array vier direto do usuário ($request->all()), um atacante poderia injetar campos como is_admin ou saldo que não deveriam ser editáveis externamente. $fillable é a whitelist que bloqueia qualquer atributo fora da lista:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Produto extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['nome', 'sku', 'preco', 'ativo', 'tags'];
protected function casts(): array
{
return [
'preco' => 'decimal:2',
'ativo' => 'boolean',
'tags' => 'array',
];
}
}
A trait SoftDeletes faz delete() gravar deleted_at em vez de remover a linha fisicamente — o Eloquent então filtra automaticamente registros com deleted_at preenchido em toda query subsequente, a menos que withTrashed() seja chamado explicitamente.
🔄 2. Attribute Casting: casts() Substitui $casts
No Laravel 11, o método casts() (em vez da antiga propriedade protected $casts) converte automaticamente tipos de coluna do banco (string/int/JSON) para tipos nativos do PHP na leitura, e de volta na escrita. 'tags' => 'array' faz o Eloquent serializar/deserializar JSON transparentemente; 'preco' => 'decimal:2' garante duas casas decimais consistentes mesmo vindo de um DECIMAL do banco.
Accessors e Mutators com Attribute::make()
A API moderna unifica getter e setter em um único método, usando closures tipadas — mais legível que o padrão antigo getXAttribute()/setXAttribute():
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function nome(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower(trim($value)),
);
}
Isso significa que $produto->nome = 'TECLADO RGB' é normalizado para 'teclado rgb' antes de ir ao banco, e ao ler $produto->nome de volta, é formatado como 'Teclado rgb' — a normalização acontece em um único lugar, não espalhada pelo código que usa o model.
Casts Customizados para Dados Sensíveis
Quando o cast embutido não é suficiente (ex.: criptografia transparente), CastsAttributes permite lógica arbitrária de get/set:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Facades\Crypt;
class EncryptedField implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return $value ? Crypt::decryptString($value) : null;
}
public function set($model, string $key, $value, array $attributes)
{
return $value ? Crypt::encryptString($value) : null;
}
}
🔍 3. Local Scopes para Consultas Reutilizáveis
Um método prefixado com scope no model vira uma extensão fluente do Query Builder, evitando repetir ->where('ativo', true) em toda parte da aplicação que precisa filtrar produtos ativos:
use Illuminate\Database\Eloquent\Builder;
public function scopeAtivos(Builder $query): void
{
$query->where('ativo', true);
}
public function scopePrecoMinimo(Builder $query, float $min): void
{
$query->where('preco', '>=', $min);
}
Uso: Produto::ativos()->precoMinimo(50)->get() — os scopes são encadeáveis como qualquer método do Query Builder.