Skip to content

Seeder System

The Seeder System provides automatic discovery, dependency resolution, and ordered execution of database seeders across modules. It supports both auto-discovery and manual registration with explicit priority and dependency declarations.

Overview

The Core seeder system offers:

  • Auto-discovery - Finds seeders in module directories automatically
  • Dependency ordering - Seeders run in dependency-resolved order
  • Priority control - Fine-grained control over execution order
  • Circular detection - Catches and reports circular dependencies
  • Filtering - Include/exclude seeders at runtime

Core Components

ClassPurpose
SeederDiscoveryAuto-discovers and orders seeders
SeederRegistryManual seeder registration
CoreDatabaseSeederBase seeder with discovery support
#[SeederPriority]Attribute for priority
#[SeederAfter]Attribute for dependencies
#[SeederBefore]Attribute for reverse dependencies
CircularDependencyExceptionThrown on circular deps

Discovery

Seeders are auto-discovered in Database/Seeders/ directories within configured module paths.

Discovery Pattern

{module_path}/*/Database/Seeders/*Seeder.php

For example, with module paths [app_path('Mod')]:

app/Mod/Blog/Database/Seeders/PostSeeder.php      // Discovered
app/Mod/Blog/Database/Seeders/CategorySeeder.php  // Discovered
app/Mod/Auth/Database/Seeders/UserSeeder.php      // Discovered

Using SeederDiscovery

php
use Core\Database\Seeders\SeederDiscovery;

$discovery = new SeederDiscovery([
    app_path('Core'),
    app_path('Mod'),
]);

// Get ordered seeders
$seeders = $discovery->discover();
// Returns: ['UserSeeder', 'CategorySeeder', 'PostSeeder', ...]

Priority System

Seeders declare priority using the #[SeederPriority] attribute or a public $priority property. Lower priority values run first.

Using the Attribute

php
use Core\Database\Seeders\Attributes\SeederPriority;
use Illuminate\Database\Seeder;

#[SeederPriority(10)]
class FeatureSeeder extends Seeder
{
    public function run(): void
    {
        // Runs early (priority 10)
    }
}

#[SeederPriority(90)]
class DemoDataSeeder extends Seeder
{
    public function run(): void
    {
        // Runs later (priority 90)
    }
}

Using a Property

php
class FeatureSeeder extends Seeder
{
    public int $priority = 10;

    public function run(): void
    {
        // Runs early
    }
}

Priority Guidelines

RangeUse CaseExamples
0-20Foundation dataFeatures, configuration, settings
20-40Core dataPackages, plans, workspaces
40-60Default (50)General module seeders
60-80Content dataPages, posts, products
80-100Demo/test dataSample content, test users

Dependency Resolution

Dependencies ensure seeders run in the correct order regardless of priority. Dependencies take precedence over priority.

Using #[SeederAfter]

Declare that this seeder must run after specified seeders:

php
use Core\Database\Seeders\Attributes\SeederAfter;
use Mod\Feature\Database\Seeders\FeatureSeeder;

#[SeederAfter(FeatureSeeder::class)]
class PackageSeeder extends Seeder
{
    public function run(): void
    {
        // Runs after FeatureSeeder
    }
}

Multiple Dependencies

php
use Mod\Feature\Database\Seeders\FeatureSeeder;
use Mod\Tenant\Database\Seeders\TenantSeeder;

#[SeederAfter(FeatureSeeder::class, TenantSeeder::class)]
class WorkspaceSeeder extends Seeder
{
    public function run(): void
    {
        // Runs after both FeatureSeeder and TenantSeeder
    }
}

Using #[SeederBefore]

Declare that this seeder must run before specified seeders. This is the inverse relationship - you're saying other seeders depend on this one:

php
use Core\Database\Seeders\Attributes\SeederBefore;
use Mod\Package\Database\Seeders\PackageSeeder;

#[SeederBefore(PackageSeeder::class)]
class FeatureSeeder extends Seeder
{
    public function run(): void
    {
        // Runs before PackageSeeder
    }
}

Using Properties

As an alternative to attributes, use public properties:

php
class WorkspaceSeeder extends Seeder
{
    public array $after = [
        FeatureSeeder::class,
        PackageSeeder::class,
    ];

    public array $before = [
        DemoSeeder::class,
    ];

    public function run(): void
    {
        // ...
    }
}

Complex Ordering Examples

Example 1: Linear Chain

php
// Run order: Feature -> Package -> Workspace -> User

#[SeederPriority(10)]
class FeatureSeeder extends Seeder { }

#[SeederAfter(FeatureSeeder::class)]
class PackageSeeder extends Seeder { }

#[SeederAfter(PackageSeeder::class)]
class WorkspaceSeeder extends Seeder { }

#[SeederAfter(WorkspaceSeeder::class)]
class UserSeeder extends Seeder { }

Example 2: Diamond Dependency

php
//        Feature
//       /       \
//   Package    Plan
//       \       /
//       Workspace

#[SeederPriority(10)]
class FeatureSeeder extends Seeder { }

#[SeederAfter(FeatureSeeder::class)]
class PackageSeeder extends Seeder { }

#[SeederAfter(FeatureSeeder::class)]
class PlanSeeder extends Seeder { }

#[SeederAfter(PackageSeeder::class, PlanSeeder::class)]
class WorkspaceSeeder extends Seeder { }

// Execution order: Feature -> [Package, Plan] -> Workspace
// Package and Plan can run in either order (same priority level)

Example 3: Priority with Dependencies

php
// Dependencies override priority

#[SeederPriority(90)]  // High priority number (normally runs late)
#[SeederBefore(DemoSeeder::class)]
class FeatureSeeder extends Seeder { }

#[SeederPriority(10)]  // Low priority number (normally runs early)
#[SeederAfter(FeatureSeeder::class)]
class DemoSeeder extends Seeder { }

// Despite priority, FeatureSeeder runs first due to dependency

Example 4: Mixed Priority and Dependencies

php
// Seeders at the same dependency level sort by priority

#[SeederPriority(10)]
class FeatureSeeder extends Seeder { }

#[SeederAfter(FeatureSeeder::class)]
#[SeederPriority(20)]  // Lower priority = runs first among siblings
class PackageSeeder extends Seeder { }

#[SeederAfter(FeatureSeeder::class)]
#[SeederPriority(30)]  // Higher priority = runs after PackageSeeder
class PlanSeeder extends Seeder { }

// Order: Feature -> Package -> Plan
// (Package before Plan because 20 < 30)

Circular Dependency Errors

Circular dependencies are detected and throw CircularDependencyException.

What Causes Circular Dependencies

php
// This creates a cycle: A -> B -> C -> A

#[SeederAfter(SeederC::class)]
class SeederA extends Seeder { }

#[SeederAfter(SeederA::class)]
class SeederB extends Seeder { }

#[SeederAfter(SeederB::class)]
class SeederC extends Seeder { }

Error Handling

php
use Core\Database\Seeders\Exceptions\CircularDependencyException;

try {
    $seeders = $discovery->discover();
} catch (CircularDependencyException $e) {
    echo $e->getMessage();
    // "Circular dependency detected in seeders: SeederA -> SeederB -> SeederC -> SeederA"

    // Get the cycle chain
    $cycle = $e->cycle;
    // ['SeederA', 'SeederB', 'SeederC', 'SeederA']
}

Debugging Circular Dependencies

  1. Check the exception message for the cycle path
  2. Review the $after and $before declarations
  3. Remember that #[SeederBefore] creates implicit after relationships
  4. Use the registry to inspect relationships:
php
$discovery = new SeederDiscovery([app_path('Mod')]);
$seeders = $discovery->getSeeders();

foreach ($seeders as $class => $meta) {
    echo "{$class}:\n";
    echo "  Priority: {$meta['priority']}\n";
    echo "  After: " . implode(', ', $meta['after']) . "\n";
    echo "  Before: " . implode(', ', $meta['before']) . "\n";
}

Manual Registration

Use SeederRegistry for explicit control over seeder ordering:

php
use Core\Database\Seeders\SeederRegistry;

$registry = new SeederRegistry();

// Register with options
$registry
    ->register(FeatureSeeder::class, priority: 10)
    ->register(PackageSeeder::class, after: [FeatureSeeder::class])
    ->register(WorkspaceSeeder::class, after: [PackageSeeder::class]);

// Get ordered list
$seeders = $registry->getOrdered();

Bulk Registration

php
$registry->registerMany([
    FeatureSeeder::class => 10,  // Priority shorthand
    PackageSeeder::class => [
        'priority' => 50,
        'after' => [FeatureSeeder::class],
    ],
    WorkspaceSeeder::class => [
        'priority' => 50,
        'after' => [PackageSeeder::class],
        'before' => [DemoSeeder::class],
    ],
]);

Registry Operations

php
// Check if registered
$registry->has(FeatureSeeder::class);

// Remove a seeder
$registry->remove(DemoSeeder::class);

// Merge registries
$registry->merge($otherRegistry);

// Clear all
$registry->clear();

CoreDatabaseSeeder

Extend CoreDatabaseSeeder for automatic discovery in your application:

Basic Usage

php
<?php

namespace Database\Seeders;

use Core\Database\Seeders\CoreDatabaseSeeder;

class DatabaseSeeder extends CoreDatabaseSeeder
{
    // Uses auto-discovery by default
}

Custom Paths

php
class DatabaseSeeder extends CoreDatabaseSeeder
{
    protected function getSeederPaths(): array
    {
        return [
            app_path('Core'),
            app_path('Mod'),
            base_path('packages/my-package/src'),
        ];
    }
}

Excluding Seeders

php
class DatabaseSeeder extends CoreDatabaseSeeder
{
    protected function getExcludedSeeders(): array
    {
        return [
            DemoDataSeeder::class,
            TestUserSeeder::class,
        ];
    }
}

Disabling Auto-Discovery

php
class DatabaseSeeder extends CoreDatabaseSeeder
{
    protected bool $autoDiscover = false;

    protected function registerSeeders(SeederRegistry $registry): void
    {
        $registry
            ->register(FeatureSeeder::class, priority: 10)
            ->register(PackageSeeder::class, priority: 20)
            ->register(UserSeeder::class, priority: 30);
    }
}

Command-Line Filtering

Filter seeders when running db:seed:

bash
# Exclude specific seeders
php artisan db:seed --exclude=DemoSeeder

# Exclude multiple
php artisan db:seed --exclude=DemoSeeder --exclude=TestSeeder

# Run only specific seeders
php artisan db:seed --only=UserSeeder

# Run multiple specific seeders
php artisan db:seed --only=UserSeeder --only=FeatureSeeder

Pattern Matching

Filters support multiple matching strategies:

bash
# Full class name
php artisan db:seed --exclude=Mod\\Blog\\Database\\Seeders\\PostSeeder

# Short name
php artisan db:seed --exclude=PostSeeder

# Partial match
php artisan db:seed --exclude=Demo  # Matches DemoSeeder, DemoDataSeeder, etc.

Configuration

Configure the seeder system in config/core.php:

php
return [
    'seeders' => [
        // Enable auto-discovery
        'auto_discover' => env('CORE_SEEDER_AUTODISCOVER', true),

        // Paths to scan
        'paths' => [
            app_path('Core'),
            app_path('Mod'),
            app_path('Website'),
        ],

        // Classes to exclude
        'exclude' => [
            // App\Mod\Demo\Database\Seeders\DemoSeeder::class,
        ],
    ],
];

Best Practices

1. Use Explicit Dependencies

php
// Preferred: Explicit dependencies
#[SeederAfter(FeatureSeeder::class)]
class PackageSeeder extends Seeder { }

// Avoid: Relying only on priority for ordering
#[SeederPriority(51)]  // Fragile - assumes FeatureSeeder is 50
class PackageSeeder extends Seeder { }

2. Keep Seeders Focused

php
// Good: Single responsibility
class PostSeeder extends Seeder {
    public function run(): void {
        Post::factory()->count(50)->create();
    }
}

// Avoid: Monolithic seeders
class EverythingSeeder extends Seeder {
    public function run(): void {
        // Creates users, posts, comments, categories, tags...
    }
}

3. Use Factories in Seeders

php
class PostSeeder extends Seeder
{
    public function run(): void
    {
        // Good: Use factories for consistent test data
        Post::factory()
            ->count(50)
            ->has(Comment::factory()->count(3))
            ->create();
    }
}

4. Handle Idempotency

php
class FeatureSeeder extends Seeder
{
    public function run(): void
    {
        // Good: Use updateOrCreate for idempotent seeding
        Feature::updateOrCreate(
            ['code' => 'blog'],
            ['name' => 'Blog', 'enabled' => true]
        );
    }
}

5. Document Dependencies

php
/**
 * Seeds packages for the tenant module.
 *
 * Requires:
 * - FeatureSeeder: Features must exist to link packages
 * - TenantSeeder: Tenants must exist to assign packages
 */
#[SeederAfter(FeatureSeeder::class, TenantSeeder::class)]
class PackageSeeder extends Seeder { }

Troubleshooting

Seeders Not Discovered

  1. Check the file is in Database/Seeders/ subdirectory
  2. Verify class name ends with Seeder
  3. Confirm namespace matches file location
  4. Check the path is included in discovery paths

Wrong Execution Order

  1. Print discovery results to verify:
    php
    $discovery = new SeederDiscovery([app_path('Mod')]);
    dd($discovery->getSeeders());
  2. Check for missing #[SeederAfter] declarations
  3. Verify priority values (lower runs first)

Circular Dependency Error

  1. Read the error message for the cycle
  2. Draw out the dependency graph
  3. Identify which relationship should be removed/reversed
  4. Consider if the circular dependency indicates a design issue

Learn More

Released under the EUPL-1.2 License.