Buy

Fixtures: Seeding Dummy Data!

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

Login Subscribe

We're creating our dummy article data in a really... uh... dummy way: with a, sort of, "secret" endpoint that creates an almost-identical article, each time we go there:

61 lines src/Controller/ArticleAdminController.php
... lines 1 - 10
class ArticleAdminController extends AbstractController
{
/**
* @Route("/admin/article/new")
*/
public function new(EntityManagerInterface $em)
{
$article = new Article();
$article->setTitle('Why Asteroids Taste Like Bacon')
->setSlug('why-asteroids-taste-like-bacon-'.rand(100, 999))
->setContent(<<<EOF
Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
**turkey** shank eu pork belly meatball non cupim.
Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
occaecat lorem meatball prosciutto quis strip steak.
Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
fugiat.
EOF
);
// publish most articles
if (rand(1, 10) > 2) {
$article->setPublishedAt(new \DateTime(sprintf('-%d days', rand(1, 100))));
}
$article->setAuthor('Mike Ferengi')
->setHeartCount(rand(5, 100))
->setImageFilename('asteroid.jpeg')
;
$em->persist($article);
$em->flush();
return new Response(sprintf(
'Hiya! New Article id: #%d slug: %s',
$article->getId(),
$article->getSlug()
));
}
}

Honestly, it's not great for development: every article on the homepage pretty much looks the same.

Yep, our dummy data sucks. And, that's important! If we could load a rich set of random data easily, we could develop and debug faster.

Installing DoctrineFixturesBundle

To help with this, we'll use a great library called DoctrineFixturesBundle... but with our own spin to make things really fun.

First let's get it installed. Find your terminal and run

composer require orm-fixtures --dev

And yep, this is a Flex alias, and we're using --dev because this tool will help us load fake data for development... which is not something we need in our production code. If you've ever accidentally replaced the production database with dummy data... you know what I mean.

Generating Fixture with make:fixtures

Perfect! When it finishes, generate our first fixture class by running:

php bin/console make:fixtures

Call it ArticleFixtures. It's fairly common to have one fixture class per entity, or sometimes, per group of entities. And... done!

Go check it out: src/DataFixtures/ArticleFixtures.php:

18 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 2
namespace App\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class ArticleFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
// $product = new Product();
// $manager->persist($product);
$manager->flush();
}
}

The idea behind fixtures is dead simple: step (1) we write code to create and save objects, and then step (2), we run a new console command that executes all of our fixture classes.

Writing the Fixtures

Open ArticleAdminController: let's start stealing some code! Copy all of our dummy article code, go back to the fixture class, and paste! We need to re-type the e on Article and hit tab so that PhpStorm adds the use statement for us on top:

51 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 4
use App\Entity\Article;
... lines 6 - 8
class ArticleFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
$article = new Article();
... lines 14 - 48
}
}

Then, at the bottom, the entity manager variable is called $manager:

51 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 8
class ArticleFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
$article = new Article();
... lines 14 - 45
$manager->persist($article);
$manager->flush();
}
}

Back in the controller, just put a die('todo') for now:

27 lines src/Controller/ArticleAdminController.php
... lines 1 - 10
class ArticleAdminController extends AbstractController
{
/**
* @Route("/admin/article/new")
*/
public function new(EntityManagerInterface $em)
{
die('todo');
... lines 19 - 24
}
}

Someday, we'll create a proper admin form here.

And... that's it! It's super boring and it only creates one article... but it should work! Try it: find your terminal and run a new console command:

php bin/console doctrine:fixtures:load

This will ask if you want to continue because - important note! - the command will empty the database first, and then load fresh data. Again, not something you want to run on production... not saying I've done that before.

When it finishes, find your browser, and refresh. It works!

Creating Multiple Articles

But... come on! We're going to need more than one article! How can we create multiple? First, we're going to do it the easy... but, kinda boring way. A for loop! Say: for $i = 0; $i < 10; $i++. And, all the way at the bottom, add the ending curly brace. We need to call persist() in the loop, but we only need to call flush() once at the end.

Cool! Try it again:

php bin/console doctrine:fixtures:load

Then, refresh! Awesome! Except that the articles are still totally boring and identical... we'll talk about that in the next chapter.

BaseFixture Class for Cooler Looping

But first, let me show you a cooler way to create multiple articles. In the DataFixtures directory, create a new class called BaseFixtures. Make it abstract, and extend the normal class that all fixtures extend... so... Fixtures:

22 lines src/DataFixtures/BaseFixture.php
... lines 1 - 2
namespace App\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture;
... lines 6 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 20
}

Here's the idea: this will not be a fixture class that the bundle will execute. Instead, it will be a base class with some cool helper methods. To start, copy the load() method and implement it here. Re-type ObjectManager to get its use statement:

22 lines src/DataFixtures/BaseFixture.php
... lines 1 - 2
namespace App\DataFixtures;
... lines 4 - 5
use Doctrine\Common\Persistence\ObjectManager;
abstract class BaseFixture extends Fixture
{
... lines 10 - 14
public function load(ObjectManager $manager)
{
... lines 17 - 19
}
}

Oh, ObjectManager is an interface implemented by EntityManager, it's not too important: just think "this is the entity manager".

Next, and this won't make sense yet, create a private $manager property, and set it inside the load() method:

22 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... line 10
private $manager;
... lines 12 - 14
public function load(ObjectManager $manager)
{
$this->manager = $manager;
... lines 18 - 19
}
}

Finally, create an abstract protected function called loadData() with that same ObjectManager argument:

22 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 12
abstract protected function loadData(ObjectManager $manager);
... lines 14 - 20
}

Back in load(), call this: $this->loadData($manager):

22 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 14
public function load(ObjectManager $manager)
{
... lines 17 - 18
$this->loadData($manager);
}
}

So far, this doesn't do anything special. Back in ArticleFixtures, extend the new BaseFixture instead. I'll also cleanup the extra use statement:

50 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 4
use App\Entity\Article;
use Doctrine\Common\Persistence\ObjectManager;
class ArticleFixtures extends BaseFixture
{
... lines 10 - 48
}

Now, instead of implementing load(), implement loadData() and make it protected:

50 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 7
class ArticleFixtures extends BaseFixture
{
public function loadData(ObjectManager $manager)
{
... lines 12 - 47
}
}

And... yea! The fixture system will call load() on BaseFixture, that will call loadData() on ArticleFixtures and... well... everything will work exactly like before.

Adding the createMany Method

So... why did we just do this? Go back to the BaseFixture class and, at the bottom, I'm going to paste in a little method that I created:

34 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 21
protected function createMany(string $className, int $count, callable $factory)
{
for ($i = 0; $i < $count; $i++) {
$entity = new $className();
$factory($entity, $i);
$this->manager->persist($entity);
// store for usage later as App\Entity\ClassName_#COUNT#
$this->addReference($className . '_' . $i, $entity);
}
}
}

Oh, and to make PhpStorm happy, at the top, add some PHPDoc that the $manager property is an ObjectManager instance:

34 lines src/DataFixtures/BaseFixture.php
... lines 1 - 5
use Doctrine\Common\Persistence\ObjectManager;
abstract class BaseFixture extends Fixture
{
/** @var ObjectManager */
private $manager;
... lines 12 - 32
}

Anyways, say hello to createMany()! A simple method that we can call to create multiple instances of an object. Here's the idea: we call createMany() and pass it the class we want to create, how many we want to create, and a callback method that will be called each time an object is created. That'll be our chance to load that object with data.

Basically, it does the for loop for us... which is not a huge deal, except for two nice things. First, it calls persist() for us, so we don't have to:

34 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 21
protected function createMany(string $className, int $count, callable $factory)
{
for ($i = 0; $i < $count; $i++) {
... lines 25 - 27
$this->manager->persist($entity);
... lines 29 - 30
}
}
}

Ok, cool, but not amazing. But, this last line is cool:

34 lines src/DataFixtures/BaseFixture.php
... lines 1 - 7
abstract class BaseFixture extends Fixture
{
... lines 10 - 21
protected function createMany(string $className, int $count, callable $factory)
{
for ($i = 0; $i < $count; $i++) {
... lines 25 - 28
// store for usage later as App\Entity\ClassName_#COUNT#
$this->addReference($className . '_' . $i, $entity);
}
}
}

It won't matter yet, but in a future tutorial, we will have multiple fixtures classes. When we do, we will need to be able to reference objects created in one fixture class from other fixture classes. By calling addReference(), all of our objects are automatically stored and ready to be fetched with a key that's their class name plus the index number.

The point is: this is going to save us some serious work... but not until the next tutorial.

Back in ArticleFixtures, use the new method: $this->createMany() passing it Article::class, 10, and a function:

49 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 7
class ArticleFixtures extends BaseFixture
{
public function loadData(ObjectManager $manager)
{
$this->createMany(Article::class, 10, function(Article $article, $count) {
... lines 13 - 43
});
... lines 45 - 46
}
}

This will have two args: the Article that was just created and a count of which article this is. Inside the method, we can remove the $article = new Article(), and instead of a random number on the slug, we can use $count:

49 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 7
class ArticleFixtures extends BaseFixture
{
public function loadData(ObjectManager $manager)
{
$this->createMany(Article::class, 10, function(Article $article, $count) {
$article->setTitle('Why Asteroids Taste Like Bacon')
->setSlug('why-asteroids-taste-like-bacon-'.$count)
->setContent(<<<EOF
Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
**turkey** shank eu pork belly meatball non cupim.
Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
occaecat lorem meatball prosciutto quis strip steak.
Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
fugiat.
EOF
);
// publish most articles
if (rand(1, 10) > 2) {
$article->setPublishedAt(new \DateTime(sprintf('-%d days', rand(1, 100))));
}
$article->setAuthor('Mike Ferengi')
->setHeartCount(rand(5, 100))
->setImageFilename('asteroid.jpeg')
;
});
... lines 45 - 46
}
}

At the bottom, the persist isn't hurting anything, but it's not needed anymore.

Finish the end with a closing parenthesis and a semicolon:

49 lines src/DataFixtures/ArticleFixtures.php
... lines 1 - 7
class ArticleFixtures extends BaseFixture
{
public function loadData(ObjectManager $manager)
{
$this->createMany(Article::class, 10, function(Article $article, $count) {
$article->setTitle('Why Asteroids Taste Like Bacon')
->setSlug('why-asteroids-taste-like-bacon-'.$count)
->setContent(<<<EOF
Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
**turkey** shank eu pork belly meatball non cupim.
Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
occaecat lorem meatball prosciutto quis strip steak.
Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
fugiat.
EOF
);
// publish most articles
if (rand(1, 10) > 2) {
$article->setPublishedAt(new \DateTime(sprintf('-%d days', rand(1, 100))));
}
$article->setAuthor('Mike Ferengi')
->setHeartCount(rand(5, 100))
->setImageFilename('asteroid.jpeg')
;
});
$manager->flush();
}
}

So, it's a little bit fancier, and it'll save that important reference for us. Let's try it! Reload the fixtures again:

php bin/console doctrine:fixtures:load

No errors! Refresh the homepage: ah, our same, boring list of 10 identical articles. In the next chapter, let's use an awesome library called Faker to give each article rich, unique, realistic data.

Leave a comment!

  • 2018-07-20 Diego Aguiar

    Hey Amy

    Nice question :)
    There is a way to run your fixtures for a production environment, but I wouldn't recommend it, it could be fatal if you use it wrongly, so, another approach would be to create a Symfony command that will load all the required information into the DB

    Here you can read how to implement your own commands: https://symfony.com/doc/2.6...

    Cheers!

  • 2018-07-19 Amy

    Fixtures seem great for test data. However, what about production-necessary default data? For example, our app has a table of workflow actions. When we deploy a new database, it should have a set of default workflow actions. Currently, I use a home-grown migration like system that we call self-healing. But, if there is a way to do it in symfony land, I'd be very interested.