Buy Access to Course
07.

Awesome Random Fixtures

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Look at ArticleFixtures: we created 10 articles. So, the system has references from 0 to 9:

66 lines | src/DataFixtures/ArticleFixtures.php
// ... lines 1 - 8
class ArticleFixtures extends BaseFixture
{
// ... lines 11 - 27
public function loadData(ObjectManager $manager)
{
$this->createMany(Article::class, 10, function(Article $article, $count) use ($manager) {
// ... lines 31 - 60
});
// ... lines 62 - 63
}
}

In CommentFixture, spice things up: replace the 0 with $this->faker->numberBetween(0, 9):

26 lines | src/DataFixtures/CommentFixture.php
// ... lines 1 - 8
class CommentFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(Comment::class, 100, function(Comment $comment) {
// ... lines 14 - 19
$comment->setArticle($this->getReference(Article::class.'_'.$this->faker->numberBetween(0, 9)));
});
// ... lines 22 - 23
}
}

Try the fixtures again:

php bin/console doctrine:fixtures:load

No errors! And... check the database:

php bin/console doctrine:query:sql 'SELECT * FROM comment'

That is much better! Just like that, each comment is related to a random article!

Making this Random Reference System Reusable

I really like this idea, where we can fetch random objects in our fixtures. So, let's make it easier! In BaseFixture, add a new private property on top called $referencesIndex. Set that to an empty array:

62 lines | src/DataFixtures/BaseFixture.php
// ... lines 1 - 9
abstract class BaseFixture extends Fixture
{
// ... lines 12 - 17
private $referencesIndex = [];
// ... lines 19 - 60
}

I'm adding this because, at the bottom of this class, I'm going to paste in a new, method that I prepared. It's a little ugly, but this new getRandomReference() does exactly what its name says: you pass it a class, like the Article class, and it will find a random Article for you:

62 lines | src/DataFixtures/BaseFixture.php
// ... lines 1 - 9
abstract class BaseFixture extends Fixture
{
// ... lines 12 - 17
private $referencesIndex = [];
// ... lines 19 - 41
protected function getRandomReference(string $className) {
if (!isset($this->referencesIndex[$className])) {
$this->referencesIndex[$className] = [];
foreach ($this->referenceRepository->getReferences() as $key => $ref) {
if (strpos($key, $className.'_') === 0) {
$this->referencesIndex[$className][] = $key;
}
}
}
if (empty($this->referencesIndex[$className])) {
throw new \Exception(sprintf('Cannot find any references for class "%s"', $className));
}
$randomReferenceKey = $this->faker->randomElement($this->referencesIndex[$className]);
return $this->getReference($randomReferenceKey);
}
}

That's super friendly!

In CommentFixture, use it: $comment->setArticle() with $this->getRandomReference(Article::class):

26 lines | src/DataFixtures/CommentFixture.php
// ... lines 1 - 8
class CommentFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(Comment::class, 100, function(Comment $comment) {
// ... lines 14 - 19
$comment->setArticle($this->getRandomReference(Article::class));
});
// ... lines 22 - 23
}
}

To make sure my function works, try the fixtures one last time:

php bin/console doctrine:fixtures:load

And, query for the comments:

php bin/console doctrine:query:sql 'SELECT * FROM comment'

Brilliant!

Fixture Ordering

There is one last minor problem with our fixtures... they only work due to pure luck. Check this out: I'll right click on CommentFixture and rename the class to A0CommentFixture:

32 lines | src/DataFixtures/A0CommentFixture.php
// ... lines 1 - 9
class A0CommentFixture extends BaseFixture implements DependentFixtureInterface
{
// ... lines 12 - 30
}

Also allow PhpStorm to rename the file. Some of you might already see the problem. Try the fixtures now:

php bin/console doctrine:fixtures:load

Bah! Explosion!

Cannot find any references to App\Entity\Article

The error comes from BaseFixture and it basically means that no articles have been set into the reference system yet!

62 lines | src/DataFixtures/BaseFixture.php
// ... lines 1 - 9
abstract class BaseFixture extends Fixture
{
// ... lines 12 - 41
protected function getRandomReference(string $className) {
// ... lines 43 - 52
if (empty($this->referencesIndex[$className])) {
throw new \Exception(sprintf('Cannot find any references for class "%s"', $className));
}
// ... lines 56 - 59
}
}

You can see the problem in the file tree. We have not been thinking at all about what order each fixture class is executed. By default, it loads them alphabetically. But now, this is a problem! The A0CommentFixture class is being loaded before ArticleFixtures... which totally ruins our cool system!

You can also see this in the terminal: it loaded A0CommentFixture first.

DependentFixtureInterface

The solution is pretty cool. As soon as you have a fixture class that is dependent on another fixture class, you need to implement an interface called DependentFixtureInterface:

32 lines | src/DataFixtures/A0CommentFixture.php
// ... lines 1 - 6
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
// ... lines 8 - 9
class A0CommentFixture extends BaseFixture implements DependentFixtureInterface
{
// ... lines 12 - 30
}

This will require you to have one method. Move to the bottom, then, go to the "Code" -> "Generate" menu, or Command + N on a Mac, select "Implement Methods" and choose getDependencies(). I'll add the public before the function:

32 lines | src/DataFixtures/A0CommentFixture.php
// ... lines 1 - 9
class A0CommentFixture extends BaseFixture implements DependentFixtureInterface
{
// ... lines 12 - 26
public function getDependencies()
{
// ... line 29
}
}

Just return an array with ArticleFixtures::class:

32 lines | src/DataFixtures/A0CommentFixture.php
// ... lines 1 - 9
class A0CommentFixture extends BaseFixture implements DependentFixtureInterface
{
// ... lines 12 - 26
public function getDependencies()
{
return [ArticleFixtures::class];
}
}

That's it! Load them again:

php bin/console doctrine:fixtures:load

Bye bye error! It loaded ArticleFixtures first and then the comments below that. The fixtures library looks at all of the dependencies and figures out an order that makes sense.

With that fixed, let's rename the class back from this ridiculous name to CommentFixture:

32 lines | src/DataFixtures/CommentFixture.php
// ... lines 1 - 9
class CommentFixture extends BaseFixture implements DependentFixtureInterface
{
// ... lines 12 - 30
}

To celebrate, move over, refresh and... awesome! 8, random comments. We rock!

Next, let's learn about some tricks to control how Doctrine fetches the comments for an article, like, their order.