404 Not Found

404 Not Found


nginx

Básico do Eloquent ORM do Laravel

Eloquent é o "tradutor de banco de dados" do Laravel—você interage usando objetos PHP, e ele os traduz em consultas SQL para execução.

1. O Que Você Vai Aprender


2. Uma História Real de um Desenvolvedor Full-Stack

(1) Problema: Concatenação de strings SQL leva a injeção e perda de dados

No início, Bob escreveu o ShopMetrics em PHP nativo—todas as consultas SQL eram construídas usando concatenação de strings: "SELECT * FROM shops WHERE id = " . $_GET['id']. Um hacker injetou 1 OR 1=1 no ID da loja de Alice, causando um vazamento de dados em todo o site. Um problema mais comum foi quando Bob esqueceu de incluir uma cláusula WHERE em uma consulta UPDATE; um único comando zerou a receita de todas as lojas, e levou um dia inteiro para restaurar os dados.

(2) A Solução Eloquent ORM

O Eloquent usa objetos PHP para interagir com bancos de dados, fornece vinculação automática de parâmetros para prevenir ataques de injeção, protege campos sensíveis com atribuição em lote e usa soft delete para prevenir exclusão acidental.

PHP
// Seguro, legível, sem possibilidade de injeção SQL
$shop = Shop::create([
    'name' => 'Alice Store',
    'tenant_id' => 1,
]);

// Proteção de atribuição em lote — apenas campos $fillable permitidos
protected $fillable = ['name', 'slug', 'tenant_id'];
// revenue NÃO está em $fillable — não pode ser definido via create()

(3) Resultado

Depois que Bob começou a usar Eloquent, o risco de injeção SQL foi eliminado, e dados acidentalmente excluídos podiam ser restaurados com um único clique usando soft delete. A quantidade de código foi reduzida de 200 linhas de SQL para 30 linhas de PHP.


3. Definição do Modelo

(1) Criar um Modelo

BASH
php artisan make:model Shop
# Cria: app/Models/Shop.php

# Com migração
php artisan make:model Shop -m
# Cria: app/Models/Shop.php + database/migrations/create_shops_table.php

(2) Configuração de Propriedades do Modelo

PHP
// app/Models/Shop.php
class Shop extends Model
{
    protected $fillable = [
        'tenant_id', 'name', 'slug', 'description', 'status', 'revenue',
    ];

    protected $guarded = ['id']; // Alternativa: bloquear campos específicos

    protected $attributes = [
        'status' => 'active',
        'revenue' => 0,
    ];

    protected $casts = [
        'revenue' => 'decimal:2',
        'is_active' => 'boolean',
        'metadata' => 'json',
        'launched_at' => 'datetime',
    ];
}
Propriedade Função Método Recomendado
$fillable Campos que permitem atribuição em lote ✅ Lista Branca
$guarded Campos onde atribuição em lote é proibida ❌ Lista Negra
$casts Conversão Automática de Tipo Obrigatório
$attributes Valor Padrão de Campo Substitui Padrão do BD

(3) Diagrama de Classes do Modelo Eloquent

100%
classDiagram
    class Model {
        +save()
        +delete()
        +update(array data)
        +fresh()
        +refresh()
        +toArray()
        +toJson()
    }
    class Shop {
        +array fillable
        +array casts
        +tenant()
        +orders()
        +products()
    }
    class SoftDeletes {
        +forceDelete()
        +restore()
        +trashed()
        +withTrashed()
        +onlyTrashed()
    }
    Model <|-- Shop
    Shop ..|> SoftDeletes : usa trait

(1) ▶ Exemplo: Modelo de Loja do ShopMetrics

PHP
// app/Models/Shop.php
class Shop extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'tenant_id', 'name', 'slug', 'description', 'status', 'revenue',
    ];

    protected $casts = [
        'revenue' => 'decimal:2',
        'metadata' => 'array',
    ];

    protected $attributes = [
        'status' => 'active',
        'revenue' => 0,
    ];

    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class);
    }

    public function products(): HasMany
    {
        return $this->hasMany(Product::class);
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->where('status', 'active');
    }
}

Saída:

TEXT
// Execução bem-sucedida

4. Operações CRUD

(1) Criar

PHP
// Método 1: create() com atribuição em lote
$shop = Shop::create([
    'tenant_id' => 1,
    'name' => 'Alice Store',
    'slug' => 'alice-store',
]);

// Método 2: new + save
$shop = new Shop();
$shop->tenant_id = 1;
$shop->name = 'Alice Store';
$shop->slug = 'alice-store';
$shop->save();

// Método 3: firstOrCreate — buscar ou criar
$shop = Shop::firstOrCreate(
    ['slug' => 'alice-store'],           // critérios de busca
    ['name' => 'Alice Store', 'tenant_id' => 1], // valores se criar
);

// Método 4: updateOrCreate — atualizar ou criar
$shop = Shop::updateOrCreate(
    ['slug' => 'alice-store'],
    ['name' => 'Alice Store Updated', 'revenue' => 5000],
);

(2) Ler

PHP
// Buscar por chave primária
$shop = Shop::find(1);
$shop = Shop::findOrFail(1);  // lança 404 se não encontrado

// Buscar por coluna
$shop = Shop::where('slug', 'alice-store')->first();
$shop = Shop::whereSlug('alice-store')->firstOrFail();

// Obter todos
$shops = Shop::all();
$shops = Shop::active()->get();  // usando scope

// Chunk para grandes conjuntos de dados
Shop::chunk(200, function ($shops) {
    foreach ($shops as $shop) {
        // Processar 200 lojas por vez
    }
});

(3) Atualizar

PHP
// Atualizar modelo único
$shop->update(['name' => 'New Name']);

// Atualizar via consulta
Shop::where('status', 'suspended')->update(['status' => 'active']);

// Incrementar/Decrementar
$shop->increment('revenue', 1500);
Shop::whereId(1)->decrement('stock', 5);

(4) Excluir

PHP
// Soft delete (define deleted_at)
$shop->delete();

// Forçar exclusão (permanente)
$shop->forceDelete();

// Restaurar soft-deleted
$shop->restore();

// Consultar com lixeira
Shop::withTrashed()->where('id', 1)->first();
Shop::onlyTrashed()->get();

(1) ▶ Exemplo: Fluxo CRUD Completo do ShopMetrics

PHP
// Criar uma loja com produtos
$shop = Shop::create([
    'tenant_id' => 1,
    'name' => 'Bob Electronics',
    'slug' => 'bob-electronics',
]);

$shop->products()->createMany([
    ['name' => 'Widget A', 'sku' => 'W-001', 'price' => 29.99],
    ['name' => 'Widget B', 'sku' => 'W-002', 'price' => 49.99],
]);

// Ler com eager loading
$shop = Shop::with('products')->whereSlug('bob-electronics')->firstOrFail();

// Atualizar loja e produto
$shop->update(['revenue' => 15000]);
$shop->products()->whereSku('W-001')->update(['price' => 34.99]);

// Soft delete e restaurar
$shop->delete();
Shop::withTrashed()->whereSlug('bob-electronics')->first()->restore();

Saída:

TEXT
// Execução bem-sucedida

5. Query Builder

(1) Consulta Condicional

PHP
$shops = Shop::where('status', 'active')
    ->where('revenue', '>', 1000)
    ->orWhere(function ($query) {
        $query->where('status', 'new')
              ->where('created_at', '>', now()->subDays(7));
    })
    ->get();

// Where dinâmico
$shops = Shop::whereStatus('active')
    ->whereRevenueGreaterThan(1000)
    ->get();

(2) Ordenação, Agrupamento e Paginação

PHP
// OrderBy
$shops = Shop::orderBy('revenue', 'desc')->get();

// GroupBy com having
$revenueByStatus = Shop::select('status', DB::raw('SUM(revenue) as total'))
    ->groupBy('status')
    ->having('total', '>', 1000)
    ->get();

// Paginação
$shops = Shop::where('tenant_id', 1)->paginate(15);
$shops = Shop::where('tenant_id', 1)->simplePaginate(15);
$shops = Shop::where('tenant_id', 1)->cursorPaginate(15);
Métodos de Paginação Executar Consulta Casos de Uso
paginate() COUNT + SELECT Número Total de Páginas Necessário
simplePaginate() Apenas SELECT Número total de páginas não necessário
cursorPaginate() SELECT com WHERE apenas Mais Eficiente para Grandes Conjuntos de Dados

(3) Subconsultas

PHP
// Subconsulta no select
$shops = Shop::select('shops.*')
    ->selectSub(
        Order::selectRaw('SUM(total)')
            ->whereColumn('shop_id', 'shops.id'),
        'orders_total'
    )
    ->get();

// Subconsulta no where
$latestOrders = Shop::where('created_at', function ($query) {
    $query->selectRaw('MAX(created_at)')
          ->from('orders')
          ->whereColumn('shop_id', 'shops.id');
})->get();

(1) ▶ Exemplo: Consulta Complexa do ShopMetrics

PHP
// Top 10 lojas por receita no tenant atual, com contagem de pedidos
$topShops = Shop::select('shops.*')
    ->selectSub(
        Order::selectRaw('COUNT(*)')
            ->whereColumn('shop_id', 'shops.id')
            ->where('created_at', '>=', now()->subDays(30)),
        'recent_orders_count'
    )
    ->where('tenant_id', tenant()->id)
    ->where('status', 'active')
    ->orderBy('revenue', 'desc')
    ->take(10)
    ->get();

Saída:

TEXT
// Execução bem-sucedida

6. Operações de Coleção

O get() do Eloquent retorna um objeto Collection, que fornece métodos encadeados mais poderosos do que arrays.

Método Função Equivalente SQL
filter() Filtrar WHERE
map() Mapear Conversão Conversão SELECT
sortBy() Ordenar ORDER BY
groupBy() Agrupar GROUP BY
sum() Somar SUM()
count() Contar COUNT()
pluck() Extrair Coluna SELECT uma coluna
unique() Remover duplicatas DISTINCT
each() Iterar e executar
reduce() Cálculo Cumulativo

(1) ▶ Exemplo: Operações Encadeadas de Coleção do ShopMetrics

PHP
// Obter todas as lojas de um tenant, filtrar e transformar
$topShops = Shop::where('tenant_id', 1)
    ->with('products')
    ->get()
    ->filter(fn ($shop) => $shop->revenue > 1000)
    ->sortByDesc('revenue')
    ->map(fn ($shop) => [
        'name' => $shop->name,
        'revenue' => $shop->revenue,
        'product_count' => $shop->products->count(),
    ])
    ->take(10);

// Agrupar lojas por status e contar
$shopsByStatus = Shop::where('tenant_id', 1)
    ->get()
    ->groupBy('status')
    ->map(fn ($group) => $group->count());
// ['active' => 15, 'suspended' => 2, 'closed' => 1]

// Extrair IDs para operação em lote
$shopIds = Shop::where('status', 'active')->pluck('id');
// [1, 2, 5, 8, 12]

Saída:

TEXT
// Execução bem-sucedida

7. Soft Delete

(1) Habilitar soft delete

PHP
// Modelo
class Shop extends Model
{
    use SoftDeletes;

    protected $casts = [
        'deleted_at' => 'datetime',
    ];
}

// Migração
$table->softDeletes(); // adiciona deleted_at TIMESTAMP NULL

(2) Operação de Soft Delete

PHP
// Excluir (soft — define deleted_at)
$shop->delete();

// Verificar se está na lixeira
$shop->trashed(); // true se soft-deleted

// Incluir registros na lixeira
Shop::withTrashed()->get();

// Apenas registros na lixeira
Shop::onlyTrashed()->get();

// Restaurar
$shop->restore();

// Exclusão permanente
$shop->forceDelete();

(1) ▶ Exemplo: Cenário de Recuperação com Soft Delete do ShopMetrics

PHP
// Alice acidentalmente excluiu uma loja
$shop = Shop::whereSlug('alice-store')->first();
$shop->delete();

// Bob ainda pode encontrá-la nos registros da lixeira
$trashed = Shop::onlyTrashed()->whereSlug('alice-store')->first();

// Restaurar a loja com todos os relacionamentos intactos
if ($trashed) {
    $trashed->restore();
    // $trashed->products ainda existem — não foram excluídos
}

Saída:

TEXT
// Execução bem-sucedida

8. Exemplo Completo: Análise de Pedidos do ShopMetrics

PHP
// ============================================
// Completo: Análise de Pedidos do ShopMetrics
// Abrange: CRUD, consultas, coleções, soft delete, scopes
// ============================================

// app/Models/Order.php
class Order extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'tenant_id', 'shop_id', 'user_id', 'order_number',
        'subtotal', 'discount', 'total', 'status', 'metadata',
    ];

    protected $casts = [
        'total' => 'decimal:2',
        'metadata' => 'array',
        'deleted_at' => 'datetime',
    ];

    public function shop(): BelongsTo
    {
        return $this->belongsTo(Shop::class);
    }

    public function scopeCompleted(Builder $query): Builder
    {
        return $query->where('status', 'completed');
    }

    public function scopeThisMonth(Builder $query): Builder
    {
        return $query->whereBetween('created_at', [
            now()->startOfMonth(), now()->endOfMonth(),
        ]);
    }
}

// Consulta de análise — relatório de receita mensal
$monthlyReport = Order::where('tenant_id', tenant()->id)
    ->completed()
    ->thisMonth()
    ->with('shop')
    ->get()
    ->groupBy('shop.name')
    ->map(fn ($orders) => [
        'shop' => $orders->first()->shop->name,
        'order_count' => $orders->count(),
        'revenue' => $orders->sum('total'),
        'avg_order' => $orders->avg('total'),
    ])
    ->sortByDesc('revenue')
    ->values();

❓ Perguntas Frequentes

P Qual é a diferença entre $fillable e $guarded?
R $fillable é uma lista branca (apenas estes campos podem ser atribuídos em lote), enquanto $guarded é uma lista negra (estes campos são proibidos). Recomendamos usar a lista branca $fillable, pois é mais segura—você deve declarar explicitamente os campos que podem receber valores.
P Quando devo usar findOrFail?
R Use findOrFail quando precisar retornar uma página 404 se nenhum registro for encontrado. Se não encontrar registros for parte da lógica de negócio normal (como uma busca sem resultados), use find e verifique se é null.
P Qual é a diferença entre métodos de Collection e métodos do query builder?
R Query builders operam no nível do banco de dados (ex: filtragem com WHERE em SQL), enquanto Collections operam na memória (ex: filtragem com filter em PHP). Grandes conjuntos de dados devem ser filtrados no nível da consulta, enquanto pequenos conjuntos de resultados podem usar métodos de Collection.
P O que acontece com os relacionamentos do modelo após um soft delete?
R Um soft delete apenas define o campo deleted_at; os dados associados permanecem no banco de dados. Uma vez que o modelo pai é restaurado, o relacionamento torna-se disponível imediatamente. Se você precisar realizar um soft delete em cascata, pode ouvir o evento deleting no método boot() do modelo.
P Como escolher entre Eloquent e Query Builder?
R Use Eloquent quando precisar de instâncias de modelo (para acessar associações ou usar traits); use Query Builder (DB::table()) quando precisar apenas de resultados simples de consulta e não precisar de modelos. Eloquent é essencialmente um wrapper em torno do Query Builder.
P Qual é a diferença entre atualizações em lote e atualizações individuais?
R Shop::where(...)->update([...]) Executa uma única instrução SQL para atualizar todos os registros correspondentes; isso é altamente eficiente. $shops->each->update([...]) Executa instruções SQL uma a uma, acionando eventos do modelo. Use atualizações individuais quando precisar acionar eventos.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um modelo Product para o ShopMetrics, defina $fillable e $casts, implemente operações CRUD completas (criar, ler, atualizar, excluir), e use Tinker para validar cada operação.

  2. Problema Avançado (⭐⭐): Escreva uma consulta para recuperar as 5 lojas com maior receita sob o tenant atual, junto com suas contagens de pedidos. Use a subconsulta selectSub e o método map da Collection para formatar a saída.

  3. Desafio (⭐⭐⭐): Implemente soft delete e restauração em cascata para o modelo Order: Quando um Order for excluído, seus OrderItems também são soft-deletados; quando o Order for restaurado, os OrderItems são restaurados junto. Implemente isso usando listeners de eventos do modelo.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%