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.