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
- Definição de Model Factory: Geração de Dados Faker e Modificação de Estado
- Código Seeder e cadeia de chamadas: $this->call()
- Fábrica de Dados Relacionados: Quando um usuário é criado, um pedido e um produto são criados ao mesmo tempo
- Isolamento de dados multi-tenant: TenantSeeder popula dados por tenant
- Fluxo de trabalho para
php artisan db:seedemigrate:fresh --seed
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.
// 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
php artisan make:factory ShopFactory
# Ou com modelo
php artisan make:factory ShopFactory --model=Shop
(2) Definindo uma Factory
// 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" | |
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
// 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:
// Execução bem-sucedida
4. Modificadores de Estado da Factory
(1) Definindo Estados
// 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
// 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
// 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:
// Execução bem-sucedida
5. Fábrica de Dados Relacionados
(1) Criar um relacionamento
// 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
// 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:
// Execução bem-sucedida
6. Orquestração do Seeder
(1) Criar um Seeder
php artisan make:seeder TenantSeeder
php artisan make:seeder ProductSeeder
(2) Escrevendo um Seeder
// 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
// 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
// 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:
// Execução bem-sucedida
7. População de Dados com Isolamento Multi-tenant
(1) Alocação por Subtenant
// 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
# 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:
# Comando executado com sucesso
8. Exemplo Completo: População Completa de Dados do ShopMetrics
// ============================================
// 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
db:seed.make() e create()?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.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.DB::table('products')->insert($data). Desative eventos do modelo: Product::withoutEvents(fn () => Product::factory()->count(1000)->create()).plans); não use Factory para gerar dados fictícios. Certifique-se de verificar o comando ao adicionar a flag --force.$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
- Factory define regras de geração de dados; Faker fornece dados aleatórios realistas
- Modificação de estado (state) define diferentes cenários de dados para o mesmo modelo
- Use
has()efor()para criar dados associados em factories de associação; o método mágicohasProducts()simplifica a sintaxe - O Seeder organiza a ordem de população, e o DatabaseSeeder chama seus Seeders filhos
- Para dados multi-tenant, popule os dados por tenant e garanta que o
tenant_idesteja corretamente associado - Use
migrate:fresh --seedno desenvolvimento; use apenasdb:seed --forceem produção
📝 Exercícios
-
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.
-
Exercício Avançado (⭐⭐): Escreva um
TenantIsolatedSeederque 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. -
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) aoOrderFactory, e distribua-os proporcionalmente no Seeder (80% completed, 15% cancelled, 5% refunded).



