Fixtures for Test Data
Fixtures for Test Data
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
To wrap up block 4, let's solve a practical problem: instead of manually entering test data through the web interface on EVERY database reset, let's generate it REPRODUCIBLY via code.
Installing the fixtures bundle
composer require --dev doctrine/doctrine-fixtures-bundleCreating a fixture class
php bin/console make:fixtures ProjectFixtures<?php
declare(strict_types=1);
namespace App\DataFixtures;
use App\Entity\Project;
use App\Entity\Task;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class ProjectFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
$project = (new Project())
->setName('Website Relaunch')
->setDescription('Complete redesign of the company website');
$manager->persist($project);
$taskTitles = ['Create design concept', 'Set up backend', 'Finalize copy'];
foreach ($taskTitles as $title) {
$task = (new Task())
->setTitle($title)
->setProject($project);
$manager->persist($task);
}
$manager->flush();
}
}Note: flush() only gets called ONCE at the END, NOT after every persist() – considerably faster with larger fixture sets, since all objects are written in ONE transaction.
Loading fixtures
php bin/console doctrine:fixtures:loadAchtung: doctrine:fixtures:load CLEARS ALL tables by default BEFORE loading the fixtures – NOT an additive operation! NEVER run this accidentally against a production database. With --append, fixtures can be added WITHOUT clearing first, if that's what you want.
Relationships between several fixture classes
If a fixture class needs objects from ANOTHER one – e.g. tasks assigned to a specific user – use object references instead of manually running fixtures in the right order:
<?php
declare(strict_types=1);
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class UserFixtures extends Fixture
{
public const string ANNA_REFERENCE = 'user-anna';
public function load(ObjectManager $manager): void
{
$anna = (new User())->setName('Anna Schmidt');
$manager->persist($anna);
$manager->flush();
$this->addReference(self::ANNA_REFERENCE, $anna);
}
}use App\DataFixtures\UserFixtures;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
class ProjectFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager): void
{
$project = (new Project())->setName('Website Relaunch');
$task = (new Task())
->setTitle('Create design concept')
->setProject($project);
// Reuse the reference from UserFixtures:
$anna = $this->getReference(UserFixtures::ANNA_REFERENCE, User::class);
$project->addMember($anna);
$manager->persist($project);
$manager->persist($task);
$manager->flush();
}
public function getDependencies(): array
{
return [UserFixtures::class];
}
}DependentFixtureInterface/getDependencies() ensures UserFixtures ALWAYS runs BEFORE ProjectFixtures – independent of the alphabetical or otherwise arbitrary default order.
Faker: generating more realistic test data
composer require --dev fakerphp/fakeruse Faker\Factory;
$faker = Factory::create('en_US');
for ($i = 0; $i < 20; $i++) {
$task = (new Task())
->setTitle($faker->sentence(4))
->setProject($project);
$manager->persist($task);
}Tipp: Faker is great for LARGE amounts of realistic-looking test data (e.g. for performance tests in block 8), but for CORE fixtures (like Anna in this chapter), fixed, named values are better – that way it stays TRACEABLE in tests (block 7) WHICH specific user is meant.
With that, block 4 (Doctrine ORM & database) is complete! Our task manager now has a complete, migration-versioned database structure with reproducible test data. Block 5 adds security – registration, login, and access control, so only project members see their own projects.