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.