Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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

Creating a fixture class

php bin/console make:fixtures ProjectFixtures
src/DataFixtures/ProjectFixtures.php
<?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:load

Achtung: 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:

src/DataFixtures/UserFixtures.php
<?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);
    }
}
src/DataFixtures/ProjectFixtures.php
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/faker
use 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.