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["ProdutoFactory::definition()"] --> B["Faker gera dados fictícios realistas"]
    B --> C["Cliente::factory()->has(Pedido::factory()->count(3))"]
    C --> D["DatabaseSeeder::run()"]
    D --> E["php artisan db:seed / migrate:fresh --seed"]

    subgraph TX["Integridade Transacional"]
        F["DB::transaction(callback, tentativas)"]
        G["lockForUpdate() — bloqueio pessimista de linha"]
        H["Rollback automático em exceção"]
    end

    E --> TX

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style D fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style G fill:#fce4ec,stroke:#e91e63,stroke-width:2px

🏛️ 1. Factories: Dados Fictícios Estruturados

Uma Model Factory define, via método definition(), um “molde” de atributos gerados pela biblioteca Faker, usada tanto em testes automatizados quanto para popular o banco em desenvolvimento:

<?php
// database/factories/ProdutoFactory.php
class ProdutoFactory extends Factory
{
    protected $model = Produto::class;

    public function definition(): array
    {
        return [
            'nome'  => $this->faker->words(3, true),
            'sku'   => $this->faker->unique()->bothify('PROD-####-??'),
            'preco' => $this->faker->randomFloat(2, 10, 500),
            'ativo' => true,
        ];
    }
}

Factories podem compor relacionamentos declarativamente com has(), o que gera automaticamente a foreign key correta:

Cliente::factory(10)
    ->has(Pedido::factory()->count(3))
    ->create(); // 10 clientes, cada um com 3 pedidos vinculados

🌱 2. Seeders: Orquestrando a Massa de Dados

O DatabaseSeeder é o ponto de entrada chamado por php artisan db:seed (ou migrate:fresh --seed, que recria o schema do zero e popula em seguida):

<?php
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        Cliente::factory(10)->has(Pedido::factory()->count(3))->create();
    }
}

Diferente de factories (voltadas a dados aleatórios/teste), seeders também são o lugar correto para dados fixos de catálogo — categorias, papéis (roles), configurações iniciais — que precisam existir de forma determinística em todo ambiente.


🔒 3. Transações Atômicas e Concorrência

Operações que tocam múltiplas tabelas (criar pedido + baixar estoque) precisam ser atômicas: ou tudo é persistido, ou nada é. DB::transaction() encapsula isso e faz rollback automático se qualquer exceção for lançada dentro da closure — sem precisar de try/catch manual com DB::commit()/DB::rollBack():

DB::transaction(function () use ($dados, $itens) {
    $pedido = Pedido::create($dados);
    foreach ($itens as $item) {
        $pedido->itens()->create($item);
    }
    return $pedido;
}, 3); // 3 = número de tentativas automáticas em caso de deadlock

O segundo argumento (tentativas) é importante em bancos com alta concorrência: se duas transações colidem em um deadlock detectado pelo motor SQL, o Laravel automaticamente relança a transação inteira do zero, até o limite informado.

Quando o requisito não é só atomicidade, mas evitar que duas requisições simultâneas leiam o mesmo valor de estoque antes de decrementá-lo (condição de corrida clássica), a solução é bloqueio pessimista com lockForUpdate(), que trava a linha no banco até o fim da transação:

DB::transaction(function () use ($id) {
    $produto = Produto::where('id', $id)->lockForUpdate()->first();
    $produto->decrement('estoque', 1);
});

Sem lockForUpdate(), duas requisições concorrentes poderiam ler estoque = 1, ambas decrementarem, e o valor final ficar incorretamente negativo — um bug de concorrência silencioso que só aparece sob carga real.