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
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
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
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
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.