databases storage and caching

Database Seeding And Factories Orientation

Seeding creates known data for development, demos, and tests. Factories create realistic rows on demand, often with sensible defaults that tests can override.

Seeds and factories help PHP developers work faster because they remove manual setup. A test can create a user, an order, and a paid invoice without depending on someone's local database.

Seeds

A seed inserts known records. This is useful for roles, permissions, default plans, demo accounts, and local development examples.

PHP example
<?php

declare(strict_types=1);

$roles = [
    ['name' => 'admin'],
    ['name' => 'customer'],
    ['name' => 'support'],
];

foreach ($roles as $role) {
    echo 'seed role: ' . $role['name'] . PHP_EOL;
}

// Prints:
// seed role: admin
// seed role: customer
// seed role: support

Seeds should be repeatable where possible. If running a seed twice creates duplicate roles, it will annoy developers and break tests.

Factories

A factory builds valid test data with defaults.

PHP example
<?php

declare(strict_types=1);

function userFactory(array $overrides = []): array
{
    return array_merge([
        'email' => 'user_' . bin2hex(random_bytes(3)) . '@example.com',
        'name' => 'Test User',
        'status' => 'active',
    ], $overrides);
}

print_r(userFactory(['status' => 'disabled']));

// Prints:
// [status] => disabled

Factories should create valid data by default. Tests can override the specific field they care about.

Seeds Are Not Migrations

A migration changes schema. A seed inserts data. Keep that distinction clear.

Some data is structural, such as required roles or feature flags. That data may need careful deployment rules because production depends on it. Demo data and test users should not be inserted into production accidentally.

Deterministic Test Data

Random data can reveal assumptions, but it can also make tests hard to debug. For tests, prefer meaningful fixed values unless randomness is part of the behaviour.

PHP example
<?php

declare(strict_types=1);

function testOrder(array $overrides = []): array
{
    return array_merge([
        'status' => 'paid',
        'total_cents' => 1999,
        'currency' => 'GBP',
    ], $overrides);
}

print_r(testOrder(['status' => 'pending']));

// Prints:
// [status] => pending

What To Check

Before moving on, make sure you can:

  • Explain the difference between seeds and migrations.
  • Use seeds for known development or reference data.
  • Use factories for test data.
  • Make factories valid by default.
  • Avoid inserting demo data into production accidentally.
  • Keep test data clear enough to debug.

Practice

Practice: Create Simple Factories

Write small PHP factory functions for users and orders.

Requirements

  • Use valid defaults.
  • Allow overrides.
  • Create an order that references a user ID.
  • Show a seed list for roles.
  • Explain which data belongs in development/test only and which might be required reference data.
Show solution

These factories create valid arrays that tests could insert through a repository or ORM.

PHP example
<?php

declare(strict_types=1);

function makeUser(array $overrides = []): array
{
    return array_merge([
        'id' => 1,
        'email' => 'sam@example.com',
        'name' => 'Sam Example',
        'status' => 'active',
    ], $overrides);
}

function makeOrder(array $overrides = []): array
{
    return array_merge([
        'id' => 1,
        'user_id' => 1,
        'status' => 'paid',
        'total_cents' => 1999,
    ], $overrides);
}

$rolesToSeed = ['admin', 'customer', 'support'];

print_r(makeUser(['email' => 'lee@example.com']));
print_r(makeOrder(['user_id' => 42, 'status' => 'pending']));
echo implode(', ', $rolesToSeed) . PHP_EOL;

// Prints:
// [email] => lee@example.com
// [user_id] => 42
// admin, customer, support

Demo users and fake orders belong in development or tests. Required roles or permissions may be reference data, but they need careful production deployment rules.