Seeder Discovery & Ordering
Core PHP Framework provides automatic seeder discovery with dependency-based ordering. Define seeder dependencies using PHP attributes and let the framework handle execution order.
Overview
Traditional Laravel seeders require manual ordering in DatabaseSeeder. Core PHP automatically discovers seeders across modules and orders them based on declared dependencies.
Traditional Approach
// database/seeders/DatabaseSeeder.php
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Manual ordering - easy to get wrong
$this->call([
WorkspaceSeeder::class,
UserSeeder::class,
CategorySeeder::class,
PostSeeder::class,
CommentSeeder::class,
]);
}
}Problems:
- Manual dependency management
- Order mistakes cause failures
- Scattered across modules but centrally managed
- Hard to maintain as modules grow
Discovery Approach
// Mod/Tenant/Database/Seeders/WorkspaceSeeder.php
#[SeederPriority(100)]
class WorkspaceSeeder extends Seeder
{
public function run(): void { /* ... */ }
}
// Mod/Blog/Database/Seeders/CategorySeeder.php
#[SeederPriority(50)]
#[SeederAfter(WorkspaceSeeder::class)]
class CategorySeeder extends Seeder
{
public function run(): void { /* ... */ }
}
// Mod/Blog/Database/Seeders/PostSeeder.php
#[SeederAfter(CategorySeeder::class)]
class PostSeeder extends Seeder
{
public function run(): void { /* ... */ }
}Benefits:
- Automatic discovery across modules
- Explicit dependency declarations
- Topological sorting handles execution order
- Circular dependency detection
- Each module manages its own seeders
Configuration
Enable Auto-Discovery
// config/core.php
'seeders' => [
'auto_discover' => env('SEEDERS_AUTO_DISCOVER', true),
'paths' => [
'Mod/*/Database/Seeders',
'Core/*/Database/Seeders',
'Plug/*/Database/Seeders',
],
'exclude' => [
'DatabaseSeeder',
'CoreDatabaseSeeder',
],
],Create Core Seeder
Create a root seeder that uses discovery:
<?php
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Core\Database\Seeders\SeederRegistry;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$registry = app(SeederRegistry::class);
// Automatically discover and order seeders
$seeders = $registry->getOrderedSeeders();
$this->call($seeders);
}
}Seeder Attributes
SeederPriority
Set execution priority (higher = runs earlier):
<?php
namespace Mod\Tenant\Database\Seeders;
use Illuminate\Database\Seeder;
use Core\Database\Seeders\Attributes\SeederPriority;
#[SeederPriority(100)]
class WorkspaceSeeder extends Seeder
{
public function run(): void
{
Workspace::factory()->count(3)->create();
}
}Priority Ranges:
100+- Foundation data (workspaces, system records)50-99- Core domain data (users, categories)1-49- Feature data (posts, comments)0- Default priority<0- Post-processing (analytics, cache warming)
SeederAfter
Run after specific seeders:
<?php
namespace Mod\Blog\Database\Seeders;
use Illuminate\Database\Seeder;
use Core\Database\Seeders\Attributes\SeederAfter;
use Mod\Tenant\Database\Seeders\WorkspaceSeeder;
#[SeederAfter(WorkspaceSeeder::class)]
class CategorySeeder extends Seeder
{
public function run(): void
{
Category::factory()->count(5)->create();
}
}SeederBefore
Run before specific seeders:
<?php
namespace Mod\Blog\Database\Seeders;
use Illuminate\Database\Seeder;
use Core\Database\Seeders\Attributes\SeederBefore;
#[SeederBefore(PostSeeder::class)]
class CategorySeeder extends Seeder
{
public function run(): void
{
Category::factory()->count(5)->create();
}
}Combining Attributes
Use multiple attributes for complex dependencies:
#[SeederPriority(50)]
#[SeederAfter(WorkspaceSeeder::class, UserSeeder::class)]
#[SeederBefore(CommentSeeder::class)]
class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()->count(20)->create();
}
}Execution Order
Topological Sorting
The framework automatically orders seeders using topological sorting:
Given seeders:
- WorkspaceSeeder (priority: 100)
- UserSeeder (priority: 90, after: WorkspaceSeeder)
- CategorySeeder (priority: 50, after: WorkspaceSeeder)
- PostSeeder (priority: 40, after: CategorySeeder, UserSeeder)
- CommentSeeder (priority: 30, after: PostSeeder, UserSeeder)
Execution order:
1. WorkspaceSeeder (priority 100)
2. UserSeeder (priority 90, depends on Workspace)
3. CategorySeeder (priority 50, depends on Workspace)
4. PostSeeder (priority 40, depends on Category & User)
5. CommentSeeder (priority 30, depends on Post & User)Resolution Algorithm
- Group seeders by priority (high to low)
- Within each priority group, perform topological sort
- Detect circular dependencies
- Execute in resolved order
Circular Dependency Detection
The framework detects and prevents circular dependencies:
// ❌ This will throw CircularDependencyException
#[SeederAfter(SeederB::class)]
class SeederA extends Seeder { }
#[SeederAfter(SeederC::class)]
class SeederB extends Seeder { }
#[SeederAfter(SeederA::class)]
class SeederC extends Seeder { }
// Error: Circular dependency detected: SeederA → SeederB → SeederC → SeederAModule Seeders
Typical Module Structure
Mod/Blog/Database/Seeders/
├── BlogSeeder.php # Optional: calls other seeders
├── CategorySeeder.php # Creates categories
├── PostSeeder.php # Creates posts
└── DemoContentSeeder.php # Creates demo dataModule Seeder Example
<?php
namespace Mod\Blog\Database\Seeders;
use Illuminate\Database\Seeder;
use Core\Database\Seeders\Attributes\SeederPriority;
use Core\Database\Seeders\Attributes\SeederAfter;
use Mod\Tenant\Database\Seeders\WorkspaceSeeder;
#[SeederPriority(50)]
#[SeederAfter(WorkspaceSeeder::class)]
class BlogSeeder extends Seeder
{
public function run(): void
{
$this->call([
CategorySeeder::class,
PostSeeder::class,
]);
}
}Environment-Specific Seeding
#[SeederPriority(10)]
class DemoContentSeeder extends Seeder
{
public function run(): void
{
// Only seed demo data in non-production
if (app()->environment('production')) {
return;
}
Post::factory()
->count(50)
->published()
->create();
}
}Conditional Seeding
Feature Flags
class AnalyticsSeeder extends Seeder
{
public function run(): void
{
if (! Feature::active('analytics')) {
$this->command->info('Skipping analytics seeder (feature disabled)');
return;
}
// Seed analytics data
}
}Configuration
class EmailSeeder extends Seeder
{
public function run(): void
{
if (! config('modules.email.enabled')) {
return;
}
EmailTemplate::factory()->count(10)->create();
}
}Database Check
class MigrationSeeder extends Seeder
{
public function run(): void
{
if (! Schema::hasTable('legacy_posts')) {
return;
}
// Migrate legacy data
}
}Factory Integration
Seeders commonly use factories:
<?php
namespace Mod\Blog\Database\Seeders;
use Illuminate\Database\Seeder;
use Mod\Blog\Models\Post;
use Mod\Blog\Models\Category;
class PostSeeder extends Seeder
{
public function run(): void
{
// Create categories first
$categories = Category::factory()->count(5)->create();
// Create posts for each category
$categories->each(function ($category) {
Post::factory()
->count(10)
->for($category)
->published()
->create();
});
// Create unpublished drafts
Post::factory()
->count(5)
->draft()
->create();
}
}Testing Seeders
Unit Testing
<?php
namespace Tests\Unit\Database\Seeders;
use Tests\TestCase;
use Mod\Blog\Database\Seeders\PostSeeder;
use Mod\Blog\Models\Post;
class PostSeederTest extends TestCase
{
public function test_creates_posts(): void
{
$this->seed(PostSeeder::class);
$this->assertDatabaseCount('posts', 20);
}
public function test_posts_have_categories(): void
{
$this->seed(PostSeeder::class);
$posts = Post::all();
$posts->each(function ($post) {
$this->assertNotNull($post->category_id);
});
}
}Integration Testing
public function test_seeder_execution_order(): void
{
$registry = app(SeederRegistry::class);
$seeders = $registry->getOrderedSeeders();
$workspaceIndex = array_search(WorkspaceSeeder::class, $seeders);
$userIndex = array_search(UserSeeder::class, $seeders);
$postIndex = array_search(PostSeeder::class, $seeders);
$this->assertLessThan($userIndex, $workspaceIndex);
$this->assertLessThan($postIndex, $userIndex);
}Circular Dependency Testing
public function test_detects_circular_dependencies(): void
{
$this->expectException(CircularDependencyException::class);
// Force circular dependency
$registry = app(SeederRegistry::class);
$registry->register([
CircularA::class,
CircularB::class,
CircularC::class,
]);
$registry->getOrderedSeeders();
}Performance
Chunking
Seed large datasets in chunks:
public function run(): void
{
$faker = Faker\Factory::create();
// Seed in chunks for better memory usage
for ($i = 0; $i < 10; $i++) {
Post::factory()
->count(100)
->create();
$this->command->info("Seeded batch " . ($i + 1) . "/10");
}
}Database Transactions
Wrap seeders in transactions for performance:
public function run(): void
{
DB::transaction(function () {
Post::factory()->count(1000)->create();
});
}Disable Event Listeners
Skip event listeners during seeding:
public function run(): void
{
// Disable events for performance
Post::withoutEvents(function () {
Post::factory()->count(1000)->create();
});
}Debugging
Verbose Output
# Show seeder execution details
php artisan db:seed --verbose
# Show discovered seeders
php artisan db:seed --show-seedersDry Run
# Preview seeder order without executing
php artisan db:seed --dry-runSeeder Registry Inspection
$registry = app(SeederRegistry::class);
// Get all discovered seeders
$seeders = $registry->getAllSeeders();
// Get execution order
$ordered = $registry->getOrderedSeeders();
// Get seeder metadata
$metadata = $registry->getMetadata(PostSeeder::class);Best Practices
1. Use Priorities for Groups
// ✅ Good - clear priority groups
#[SeederPriority(100)] // Foundation
class WorkspaceSeeder { }
#[SeederPriority(50)] // Core domain
class CategorySeeder { }
#[SeederPriority(10)] // Feature data
class PostSeeder { }2. Explicit Dependencies
// ✅ Good - explicit dependencies
#[SeederAfter(WorkspaceSeeder::class, CategorySeeder::class)]
class PostSeeder { }
// ❌ Bad - implicit dependencies via priority alone
#[SeederPriority(40)]
class PostSeeder { }3. Idempotent Seeders
// ✅ Good - safe to run multiple times
public function run(): void
{
if (Category::exists()) {
return;
}
Category::factory()->count(5)->create();
}
// ❌ Bad - creates duplicates
public function run(): void
{
Category::factory()->count(5)->create();
}4. Environment Awareness
// ✅ Good - respects environment
public function run(): void
{
$count = app()->environment('production') ? 10 : 100;
Post::factory()->count($count)->create();
}5. Meaningful Names
// ✅ Good names
class WorkspaceSeeder { }
class BlogDemoContentSeeder { }
class LegacyPostMigrationSeeder { }
// ❌ Bad names
class Seeder1 { }
class TestSeeder { }
class DataSeeder { }Running Seeders
# Run all seeders
php artisan db:seed
# Run specific seeder
php artisan db:seed --class=PostSeeder
# Fresh database with seeding
php artisan migrate:fresh --seed
# Seed specific modules
php artisan db:seed --module=Blog
# Seed with environment
php artisan db:seed --env=staging