Doctrine Fixtures - Part 2 - Relating Wallpapers with Categories


Towards the end of the previous video we had created our Category entity, and added a new class property to Wallpaper to allow our entities to be related. We also created Doctrine Migrations to make sure this change was tracked and managed.

With these changes in place we can create our first Category entity, and persist it to our database. Let's do that by way of a new fixture file:

<?php

// /src/AppBundle/DataFixtures/ORM/LoadCategoryFixtures.php

namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\Category;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

class LoadCategoryFixtures implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $category = (new Category())
            ->setName('Abstract')
        ;

        $manager->persist($category);
        $manager->flush();
    }
}

As there are no generators for generating fixture files, I find a copy / paste of an existing fixture is often a good starting point. A PhpStorm Live Template is another good idea.

Now, let's reload our fixtures. And watch out, because there's a fun gotcha about to happen here:

php bin/console doctrine:fixtures:load

Careful, database will be purged. Do you want to continue y/N ?y

  > purging database
  > loading AppBundle\DataFixtures\ORM\LoadCategoryFixtures
  > loading AppBundle\DataFixtures\ORM\LoadWallpaperFixtures

Things look ok, right?

We have our new category table entry as expected.

If we look inside the wallpaper table, our entry is still there. But wait: the id field is now 2 instead of 1. What the...

Yeah this is a weird one. The message:

"Careful, database will be purged"

Is a little misleading in my opinion. It will remove all your data and repopulate it, but it won't do so by truncate.

What's the upshot of this?

Well, we cannot rely on the id field being 1 for our first fixture, 2 for our second, 3 for our third, and so on.

Thankfully though, because we aren't the first folk to encounter such a problem, there is a solution: References.

Simply put, when creating any particular object we can assign a unique reference to it, and then call that reference where needed in different fixtures.

But wait... another gotcha!

We must now apply our fixtures in some form of order. We cannot refer to references that don't yet exist.

This is quite an easy problem to solve at this stage, but becomes a little less fun on bigger projects.

In our world, we want to assign a Wallpaper to a Category.

Thinking about it then, a Category must exist in order for us to relate our Wallpaper with it.

Therefore, our Category fixture must be run before our Wallpaper fixture.

Fortunately, doing this is easy. We need to implement OrderedFixtureInterface, which has an extra method - getOrder, from which we need to return an integer value where the lower the number, the sooner that this fixture is loaded.

I tend to work in 100 increments as it allows a little finer grained tweaking as our project grows. Use whatever you prefer though:

<?php

namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\Category;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

class LoadCategoryFixtures extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $category = (new Category())
            ->setName('Abstract')
        ;

        $manager->persist($category);
        $manager->flush();
    }

    public function getOrder()
    {
        return 100;
    }
}

100 comes first because it's lower than 200...

<?php

namespace AppBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use AppBundle\Entity\Wallpaper;

class LoadWallpaperFixtures extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $wallpaper = (new Wallpaper())
            ->setFilename('abstract-background-pink.jpg')
            ->setSlug('abstract-background-pink')
            ->setWidth(1920)
            ->setHeight(1080)
        ;

        $manager->persist($wallpaper);
        $manager->flush();
    }

    public function getOrder()
    {
        return 200;
    }
}

And when we come to apply these fixtures:

php bin/console doctrine:fixtures:load

Careful, database will be purged. Do you want to continue y/N ?y

  > purging database
  > loading [100] AppBundle\DataFixtures\ORM\LoadCategoryFixtures
  > loading [200] AppBundle\DataFixtures\ORM\LoadWallpaperFixtures

We now also see the ordering info on the output.

Cool, but we haven't actually related anything just yet.

Yup, still a little extra work to do.

If we start by adding a reference to the Category fixture, we can then use that reference inside the Wallpaper fixture:

<?php

namespace AppBundle\DataFixtures\ORM;

use AppBundle\Entity\Category;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

class LoadCategoryFixtures extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $category = (new Category())
            ->setName('Abstract')
        ;

        $this->addReference('category.abstract', $category);

        $manager->persist($category);
        $manager->flush();
    }

    public function getOrder()
    {
        return 100;
    }
}

And then using that reference is easy enough:

<?php

namespace AppBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use AppBundle\Entity\Wallpaper;

class LoadWallpaperFixtures extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $wallpaper = (new Wallpaper())
            ->setFilename('abstract-background-pink.jpg')
            ->setSlug('abstract-background-pink')
            ->setWidth(1920)
            ->setHeight(1080)
            ->setCategory(
                $this->getReference('category.abstract')
            )
        ;

        $manager->persist($wallpaper);
        $manager->flush();
    }

    public function getOrder()
    {
        return 200;
    }
}

Now when we run the fixtures our entities are related as expected.

If you're following along with my images then good news, I'm about to save you a whole bunch of time. If not, then you have some typing ahead of you.

Here are links to my finished fixtures:

With this data setup and usable, in the very next video we're going to move on to creating an Admin Panel by making use of Javier Eguiluz's EasyAdminBundle.

Code For This Course

Get the code for this course.

Episodes

# Title Duration
1 Introduction and Site Demo 02:14
2 Setup and a Basic Wallpaper Gallery 08:43
3 Pagination 08:24
4 Adding a Detail View 04:47
5 Creating a Home Page 11:14
6 Creating our Wallpaper Entity 07:50
7 Wallpaper Setup Command - Part 1 - Symfony Commands As a Service 05:57
8 Wallpaper Setup Command - Part 2 - Injection Is Easy 08:53
9 Wallpaper Setup Command - Part 3 - Doing It With Style 05:37
10 Doctrine Fixtures - Part 1 - Setup and Category Entity Creation 08:52
11 Doctrine Fixtures - Part 2 - Relating Wallpapers with Categories 05:56
12 EasyAdminBundle - Setup and Category Configuration 06:02
13 EasyAdminBundle - Wallpaper Setup and List View 07:46
14 EasyAdminBundle - Starting with Wallpaper Uploads 05:57
15 Testing with PhpSpec to Guide Our Implementation 03:39
16 Using PhpSpec to Test our FileMover 05:34
17 Symfony Dependency Testing with PhpSpec 08:47
18 Defensive Counter Measures 06:33
19 No Tests - Part 1 - Uploading Files in EasyAdminBundle 11:01
20 No Tests - Part 2 - Uploading Files in EasyAdminBundle 07:05
21 Don't Mock What You Don't Own 09:36
22 You've Got To Take The Power Back 07:36
23 Making Symfony Work For Us 08:56
24 Testing The Wallpaper File Path Helper 15:11
25 Finally, It Works! 14:56
26 Why I Prefer Not To Use Twig 16:51
27 Fixing The Fixtures 11:20
28 Untested Updates 14:30
29 Untested Updates Part Two - Now We Can Actually Update 06:33
30 Adding an Image Preview On Edit 12:31
31 Delete Should Remove The Wallpaper Image File 11:02
32 Getting Started Testing Wallpaper Updates 10:02
33 Doing The Little Before The Big 08:13
34 Tested Image Preview... Sort Of :) 07:36
35 Finishing Up With a Tested Wallpaper Update 10:41
36 Test Driven Wallpaper Delete - Part 1 11:06
37 Test Driven Wallpaper Delete - Part 2 11:57
38 EasyAdminBundle Login Form Tutorial 08:01