404 Not Found

404 Not Found


nginx

Semeadura de Dados e Model Factories do Laravel

Seeder e Factory são os "geradores de dados" do Laravel—com apenas um comando, você pode gerar milhares de registros de dados de teste realistas, transformando instantaneamente seu ambiente de desenvolvimento em um cenário real.

1. O Que Você Vai Aprender


2. Uma História Real de um Testador

(1) Problema: Gerar dados manualmente é ainda mais lento do que escrever código

Toda vez que Alice testa o ShopMetrics, ela tem que criar manualmente tenants, adicionar lojas, inserir produtos e fazer pedidos—leva 30 minutos para gerar 10 registros de teste. Bob está pior; ele precisava testar a funcionalidade de paginação, mas após inserir manualmente 100 registros, seu navegador travou e perdeu todos os dados. Charlie tentou escrever um script SQL para popular os dados, mas os relacionamentos eram complexos demais (tenant → loja → produto → pedido → item do pedido). Ele escreveu 500 linhas de SQL e ainda assim perdeu uma chave estrangeira.

(2) A Abordagem Factory + Seeder

As Factories do Laravel usam Faker para gerar automaticamente dados realistas; os Seeders orquestram a ordem de população, e dados relacionados são criados com apenas uma linha de código.

PHP
// Factory gera dados realistas
$shop = Shop::factory()->create();
$shop->products()->createMany(
    Product::factory()->count(10)->make()->toArray()
);
// 1 loja + 10 produtos em 2 linhas

(3) Resultado

Após usar Factory, Alice gerou 1.000 registros de teste (incluindo todos os relacionamentos) em 30 segundos, e testar paginação exigiu apenas migrate:fresh --seed.


3. Model Factory

(1) Criar uma Factory

BASH
php artisan make:factory ShopFactory
# Ou com modelo
php artisan make:factory ShopFactory --model=Shop

(2) Definindo uma Factory

PHP
// database/factories/ShopFactory.php
class ShopFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'description' => fake()->paragraph(),
            'status' => fake()->randomElement(['active', 'suspended', 'closed']),
            'revenue' => fake()->randomFloat(2, 100, 100000),
        ];
    }
}

(3) Estratégias Comuns do Faker

Método Saída de Exemplo Descrição
name() "Alice Johnson" Nome
email() "alice@example.com" E-mail
company() "Acme Corp" Nome da Empresa
slug() "acme-corp" Slug de URL
paragraph() "Lorem ipsum..." Texto de parágrafo
randomFloat(2, 0, 100) 45.67 Decimal
numberBetween(1, 100) 42 Intervalo de inteiros
randomElement([...]) Selecionar elemento aleatório de um array Valores de enumeração
dateTimeThisYear() "15 de junho de 2024" Data
imageUrl() "https://via.placeholder.com/640x480" URL de imagem
unique() Garantir Unicidade Modificador

(1) ▶ Exemplo: Definição de Factory Principal do ShopMetrics

PHP
// database/factories/TenantFactory.php
class TenantFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'domain' => fake()->unique()->domainName(),
            'status' => 'active',
        ];
    }
}

// database/factories/ProductFactory.php
class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->words(3, true),
            'sku' => strtoupper(fake()->unique()->lexify('???-????')),
            'price' => fake()->randomFloat(2, 9.99, 999.99),
            'stock' => fake()->numberBetween(0, 500),
            'is_active' => true,
        ];
    }
}

// database/factories/OrderFactory.php
class OrderFactory extends Factory
{
    public function definition(): array
    {
        return [
            'order_number' => fake()->unique()->numerify('ORD-########'),
            'subtotal' => fake()->randomFloat(2, 10, 5000),
            'discount' => 0,
            'total' => fake()->randomFloat(2, 10, 5000),
            'status' => fake()->randomElement(['pending', 'processing', 'completed', 'cancelled']),
        ];
    }
}

Saída:

TEXT
// Execução bem-sucedida

4. Modificadores de Estado da Factory

(1) Definindo Estados

PHP
// database/factories/ShopFactory.php
class ShopFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->company(),
            'slug' => fake()->unique()->slug(),
            'status' => 'active',
            'revenue' => fake()->randomFloat(2, 100, 50000),
        ];
    }

    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'suspended',
            'revenue' => 0,
        ]);
    }

    public function highRevenue(): static
    {
        return $this->state(fn (array $attributes) => [
            'revenue' => fake()->randomFloat(2, 50000, 500000),
        ]);
    }
}

(2) Usando Estados

PHP
// Loja ativa (padrão)
$shop = Shop::factory()->create();

// Loja suspensa
$shop = Shop::factory()->suspended()->create();

// Loja de alta receita
$shop = Shop::factory()->highRevenue()->create();

// Combinar estados
$shop = Shop::factory()->highRevenue()->suspended()->create();
Uso Descrição
factory()->create() Estado padrão, armazenado no BD
factory()->make() Estado padrão, não armazenado no BD
factory()->suspended()->create() Usando o estado "suspended"
factory()->count(10)->create() Criadas 10 entradas
factory()->for(Tenant::factory()) Associar ao Modelo Pai

(1) ▶ Exemplo: Combinações de Estados da Factory do ShopMetrics

PHP
// database/factories/OrderFactory.php
class OrderFactory extends Factory
{
    public function definition(): array
    {
        return [
            'order_number' => fake()->unique()->numerify('ORD-########'),
            'subtotal' => fake()->randomFloat(2, 10, 5000),
            'discount' => 0,
            'total' => fake()->randomFloat(2, 10, 5000),
            'status' => 'pending',
        ];
    }

    public function completed(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'completed',
        ]);
    }

    public function cancelled(): static
    {
        return $this->state(fn (array $attributes) => [
            'status' => 'cancelled',
            'total' => 0,
            'subtotal' => 0,
        ]);
    }

    public function highValue(): static
    {
        return $this->state(fn (array $attributes) => [
            'subtotal' => fake()->randomFloat(2, 1000, 10000),
            'total' => fake()->randomFloat(2, 1000, 10000),
        ]);
    }
}

// Uso
$order = Order::factory()->completed()->highValue()->create();

Saída:

TEXT
// Execução bem-sucedida

5. Fábrica de Dados Relacionados

(1) Criar um relacionamento

PHP
// Criar loja com produtos
$shop = Shop::factory()
    ->has(Product::factory()->count(10), 'products')
    ->create();

// Ou usando método mágico
$shop = Shop::factory()
    ->hasProducts(10)
    ->create();

// Criar pedido para uma loja específica
$order = Order::factory()
    ->for($shop)
    ->create();

// Criar usuário com tenant
$user = User::factory()
    ->for(Tenant::factory())
    ->create();
Método Descrição Exemplo
has() Criar Sub-Associação has(Product::factory()->count(5))
hasProducts(5) Abreviação de método mágico Equivalente a has(Product::factory()->count(5), 'products')
for() Associar Modelo Pai for($tenant) ou for(Tenant::factory())

(1) ▶ Exemplo: Fábrica de Integração de Dados do ShopMetrics

PHP
// Criar um ecossistema completo de tenant
$tenant = Tenant::factory()
    ->has(User::factory()->count(3))
    ->has(Shop::factory()->count(5)->hasProducts(10))
    ->create();

// Criar um pedido com itens para uma loja
$shop = Shop::factory()->create();
$products = Product::factory()->count(5)->for($shop)->create();

$order = Order::factory()
    ->for($shop->tenant)
    ->for($shop)
    ->for(User::factory()->for($shop->tenant))
    ->has(OrderItem::factory()->count(3)->state([
        'product_id' => $products->random()->id,
    ]), 'items')
    ->create();

Saída:

TEXT
// Execução bem-sucedida

6. Orquestração do Seeder

(1) Criar um Seeder

BASH
php artisan make:seeder TenantSeeder
php artisan make:seeder ProductSeeder

(2) Escrevendo um Seeder

PHP
// database/seeders/TenantSeeder.php
class TenantSeeder extends Seeder
{
    public function run(): void
    {
        Tenant::factory()
            ->count(10)
            ->has(User::factory()->count(3))
            ->has(Shop::factory()->count(5)->hasProducts(10))
            ->create();
    }
}

(3) Cadeia de Chamadas

PHP
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            PlanSeeder::class,
            TenantSeeder::class,
            ProductSeeder::class,
            OrderSeeder::class,
        ]);
    }
}

(1) ▶ Exemplo: DatabaseSeeder Completo do ShopMetrics

PHP
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // 1. Criar planos
        Plan::factory()->createMany([
            ['name' => 'Starter', 'slug' => 'starter', 'price' => 29, 'shop_limit' => 5],
            ['name' => 'Pro', 'slug' => 'pro', 'price' => 79, 'shop_limit' => 25],
            ['name' => 'Enterprise', 'slug' => 'enterprise', 'price' => 199, 'shop_limit' => null],
        ]);

        // 2. Criar tenants com dados completos
        Tenant::factory()
            ->count(20)
            ->has(User::factory()->count(5))
            ->has(Shop::factory()->count(3)->hasProducts(15))
            ->create();

        // 3. Criar pedidos para cada loja
        Shop::all()->each(function ($shop) {
            Order::factory()
                ->count(50)
                ->for($shop->tenant)
                ->for($shop)
                ->for($shop->tenant->users->random())
                ->create();
        });
    }
}

Saída:

TEXT
// Execução bem-sucedida

7. População de Dados com Isolamento Multi-tenant

(1) Alocação por Subtenant

PHP
// database/seeders/TenantIsolatedSeeder.php
class TenantIsolatedSeeder extends Seeder
{
    public function run(): void
    {
        $tenants = Tenant::factory()->count(10)->create();

        foreach ($tenants as $tenant) {
            // Cada tenant recebe dados isolados
            $users = User::factory()->count(5)->for($tenant)->create();
            $shops = Shop::factory()->count(3)->for($tenant)->create();

            foreach ($shops as $shop) {
                $products = Product::factory()->count(20)->for($shop)->create();

                // Criar pedidos com itens
                Order::factory()->count(30)->for($tenant)->for($shop)
                    ->for($users->random())
                    ->create()
                    ->each(function ($order) use ($products) {
                        $orderItems = $products->random(rand(1, 5));
                        foreach ($orderItems as $product) {
                            $order->items()->create([
                                'product_id' => $product->id,
                                'quantity' => rand(1, 3),
                                'price' => $product->price,
                            ]);
                        }
                        $order->update([
                            'subtotal' => $order->items->sum(fn ($i) => $i->price * $i->quantity),
                            'total' => $order->items->sum(fn ($i) => $i->price * $i->quantity),
                        ]);
                    });
            }
        }
    }
}

(2) Planejamento de Volume de Dados

Entidade Por Tenant Total para 10 Tenants Observações
Usuários 5 50 incluindo 1 proprietário
Lojas 3 30 Todas ativas
Produtos 20/loja 600 Ativo/inativo aleatório
Pedidos 30/loja 900 Vários status
OrderItems 1-5 por pedido ~2.700 Quantidade aleatória

(1) ▶ Exemplo: Executando um Fluxo de Trabalho de População

BASH
# Migração limpa + seed (desenvolvimento)
php artisan migrate:fresh --seed

# Executar seeder específico
php artisan db:seed --class=TenantSeeder

# Executar com quantidade personalizada
php artisan db:seed --class=OrderSeeder

# Produção — nunca use migrate:fresh!
php artisan migrate --force
php artisan db:seed --force

Saída:

TEXT
# Comando executado com sucesso

8. Exemplo Completo: População Completa de Dados do ShopMetrics

PHP
// ============================================
// Completo: Seeder Completo do ShopMetrics
// Abrange: factories, estados, relacionamentos, isolamento de tenant
// ============================================

// database/seeders/ShopMetricsSeeder.php
class ShopMetricsSeeder extends Seeder
{
    public function run(): void
    {
        // Etapa 1: Planos
        $starter = Plan::create(['name' => 'Starter', 'slug' => 'starter', 'price' => 29, 'shop_limit' => 5, 'features' => ['basic_analytics']]);
        $pro = Plan::create(['name' => 'Pro', 'slug' => 'pro', 'price' => 79, 'shop_limit' => 25, 'features' => ['advanced_analytics', 'api_access']]);
        $enterprise = Plan::create(['name' => 'Enterprise', 'slug' => 'enterprise', 'price' => 199, 'shop_limit' => null, 'features' => ['custom_analytics', 'api_access', 'sso']]);

        // Etapa 2: Criar super admin
        User::factory()->create(['email' => 'admin@shopmetrics.io', 'role' => 'super_admin']);

        // Etapa 3: Criar tenants com dados isolados
        Tenant::factory()->count(20)->create()->each(function ($tenant) use ($starter, $pro, $enterprise) {
            // Atribuir plano aleatório
            $plan = fake()->randomElement([$starter, $pro, $enterprise]);
            Subscription::create(['tenant_id' => $tenant->id, 'plan_id' => $plan->id, 'status' => 'active']);

            // Criar proprietário do tenant
            $owner = User::factory()->for($tenant)->create(['role' => 'tenant_owner']);

            // Criar analistas
            $analysts = User::factory()->count(3)->for($tenant)->create(['role' => 'analyst']);

            // Criar lojas com produtos
            $shopCount = min(fake()->numberBetween(2, 5), $plan->shop_limit ?? 99);
            Shop::factory()->count($shopCount)->for($tenant)->create()->each(function ($shop) use ($tenant, $owner, $analysts) {
                // Produtos por loja
                Product::factory()->count(fake()->numberBetween(10, 30))->for($shop)->create();

                // Pedidos por loja
                Order::factory()->count(fake()->numberBetween(20, 80))
                    ->for($tenant)
                    ->for($shop)
                    ->for(fake()->randomElement(array_merge([$owner], $analysts->all())))
                    ->create()
                    ->each(function ($order) use ($shop) {
                        $products = $shop->products()->inRandomOrder()->take(rand(1, 5))->get();
                        foreach ($products as $product) {
                            $qty = rand(1, 3);
                            $order->items()->create([
                                'product_id' => $product->id,
                                'quantity' => $qty,
                                'price' => $product->price,
                            ]);
                        }
                        $subtotal = $order->items->sum(fn ($i) => $i->price * $i->quantity);
                        $order->update(['subtotal' => $subtotal, 'total' => $subtotal]);
                    });
            });
        });
    }
}

❓ Perguntas Frequentes

P Qual é a diferença entre Factory e Seeder?
R Uma Factory define as regras para gerar registros de dados individuais (como um molde), enquanto um Seeder orquestra a ordem e quantidade de geração de dados (como uma linha de montagem). Uma Factory pode ser usada diretamente em testes, enquanto um Seeder é executado usando o comando db:seed.
P Qual é a diferença entre make() e create()?
R make() cria uma instância do modelo mas não a salva no banco de dados; é usado em cenários onde persistência não é necessária. create() cria a instância do modelo e a salva no banco de dados; é usado em cenários onde um ID e consultas de join são necessários.
P Como gerar dados de teste em português?
R Altere o idioma do Faker para fake('pt_BR')->name(), ou use fake()->locale('pt_BR') na Factory. No entanto, recomendamos manter os dados em inglês para fins de internacionalização.
P O que devo fazer se o Seeder estiver lento?
R Reduza o número de chamadas de Factory e mude para inserções em lote: DB::table('products')->insert($data). Desative eventos do modelo: Product::withoutEvents(fn () => Product::factory()->count(1000)->create()).
P O Seeder pode ser usado em ambiente de produção?
R Sim, mas com cautela. Em ambiente de produção, use Seeder apenas para adicionar dados iniciais (como na tabela plans); não use Factory para gerar dados fictícios. Certifique-se de verificar o comando ao adicionar a flag --force.
P Como usar Factory em testes?
R Chame Factory diretamente dentro do seu método de teste: $shop = Shop::factory()->create(). O Laravel reverte automaticamente a transação do banco de dados após cada teste, para que os testes não afetem uns aos outros.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie uma ShopFactory e uma ProductFactory para o ShopMetrics, use Tinker para gerar 3 lojas com 5 produtos cada, e verifique se os dados associados estão corretos.

  2. Exercício Avançado (⭐⭐): Escreva um TenantIsolatedSeeder que gere 5 tenants, cada um contendo 2 lojas, 10 produtos e 20 pedidos, usando métodos de associação de factory (has/for) para criar os dados.

  3. Desafio (⭐⭐⭐): Implemente uma solução completa de semeadura de dados que leve em conta o estado—adicione três estados (active, suspended e highRevenue) ao ShopFactory, e três estados (completed, cancelled e refunded) ao OrderFactory, e distribua-os proporcionalmente no Seeder (80% completed, 15% cancelled, 5% refunded).

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%