Using Faker for Seeding Data
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe problem now is that our dummy data is super, duper boring. It's all the same stuff, over, and over again. Honestly, I keep falling asleep when I see the homepage. Obviously, as good PHP developers, you guys know that we could put some random code here and there to spice things up. I mean, we do already have a random $publishedAt
date:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 13 - 35 | |
if (rand(1, 10) > 2) { | |
$article->setPublishedAt(new \DateTime(sprintf('-%d days', rand(1, 100)))); | |
} | |
// ... lines 39 - 43 | |
}); | |
// ... lines 45 - 46 | |
} | |
} |
But, instead of creating that random data by hand, there's a much cooler way. We're going to use a library called Faker. Google for "Faker PHP" to find the GitHub page from Francois Zaninotto. Fun fact, Francois was the original documentation lead for symfony 1. He's awesome.
Anyways, this library is all about creating dummy data. Check it out: you can use it to generate random names, random addresses, random text, random letters, numbers between this and that, paragraphs, street codes and even winning lottery numbers! Basically, it's awesome.
Installing Faker
So let's get it installed. Copy the composer require line, move over and paste. But, add the --dev
at the end:
composer require fzaninotto/faker --dev
Because we're going to use this library for our fixtures only - it's not needed on production.
Setting up Faker
When that finishes, head back to its docs so we can see how to use it. Ok: we just need to say $faker =
Faker\Factory::create(). Open our BaseFixture
class: let's setup Faker in this, central spot. Create a new protected $faker
property:
// ... lines 1 - 9 | |
abstract class BaseFixture extends Fixture | |
{ | |
// ... lines 12 - 15 | |
protected $faker; | |
// ... lines 17 - 38 | |
} |
And down below, I'll say, $this->faker =
and look for a class called Factory
from Faker, and ::create()
:
// ... lines 1 - 6 | |
use Faker\Factory; | |
// ... lines 8 - 9 | |
abstract class BaseFixture extends Fixture | |
{ | |
// ... lines 12 - 15 | |
protected $faker; | |
// ... lines 17 - 19 | |
public function load(ObjectManager $manager) | |
{ | |
$this->manager = $manager; | |
$this->faker = Factory::create(); | |
// ... lines 24 - 25 | |
} | |
// ... lines 27 - 38 | |
} |
We should also add some PHPDoc above the property to help PhpStorm know what type of object it is. Hold Command
- or Ctrl
- and click the create()
method: let's see what this returns exactly. Apparently, it returns a Generator
.
Cool! Above the property, add /** @var Generator */
- the one from Faker
:
// ... lines 1 - 7 | |
use Faker\Generator; | |
abstract class BaseFixture extends Fixture | |
{ | |
// ... lines 12 - 14 | |
/** @var Generator */ | |
protected $faker; | |
// ... lines 17 - 38 | |
} |
Perfect! Now, using Faker will be as easy as pie! Specifically, eating pie, cause, that's super easy.
Generating Fake Data
Open ArticleFixtures
. We already have a little bit of randomness. But, Faker can even help here: change this to if $this->faker->boolean()
where the first argument is the chance of getting true
. Let's use 70: a 70% chance that each article will be published:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 13 - 34 | |
// publish most articles | |
if ($this->faker->boolean(70)) { | |
// ... line 37 | |
} | |
// ... lines 39 - 43 | |
}); | |
// ... lines 45 - 46 | |
} | |
} |
And below, we had this long expression to create a random date. Now say, $this->faker->dateTimeBetween('-100 days', '-1 days')
:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 13 - 34 | |
// publish most articles | |
if ($this->faker->boolean(70)) { | |
$article->setPublishedAt($this->faker->dateTimeBetween('-100 days', '-1 days')); | |
} | |
// ... lines 39 - 43 | |
}); | |
// ... lines 45 - 46 | |
} | |
} |
I love it! Down for heartCount
, use another Faker function: $this->faker->numberBetween(5, 100)
:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 13 - 39 | |
$article->setAuthor('Mike Ferengi') | |
->setHeartCount($this->faker->numberBetween(5, 100)) | |
// ... line 42 | |
; | |
}); | |
// ... lines 45 - 46 | |
} | |
} |
After these few improvements, let's make sure the system is actually as easy as pie. Find your terminal and run:
php bin/console doctrine:fixtures:load
No errors and... back on the browser, it works! Of course, the big problem is that the title, author and article images are always the same. Snooze.
Faker does have methods to generate random titles, random names and even random images. But, the more realistic you make your fake data, the easier it will be to build real features for your app.
Generating Controller, Realistic Data
So here's the plan: go back to ArticleFixtures
. At the top, I'm going to paste in a few static properties:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
private static $articleTitles = [ | |
'Why Asteroids Taste Like Bacon', | |
'Life on Planet Mercury: Tan, Relaxing and Fabulous', | |
'Light Speed Travel: Fountain of Youth or Fallacy', | |
]; | |
private static $articleImages = [ | |
'asteroid.jpeg', | |
'mercury.jpeg', | |
'lightspeed.png', | |
]; | |
private static $articleAuthors = [ | |
'Mike Ferengi', | |
'Amy Oort', | |
]; | |
// ... lines 26 - 64 | |
} |
These represent some realistic article titles, article images that exist, and two article authors. So, instead of making completely random titles, authors and images, we'll randomly choose from this list.
But even here, Faker can help us. For title, say $this->faker->randomElement()
and pass self::$articleTitles
:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 10 - 26 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
$article->setTitle($this->faker->randomElement(self::$articleTitles)) | |
// ... lines 31 - 60 | |
}); | |
// ... lines 62 - 63 | |
} | |
} |
We'll let Faker do all the hard work.
For setSlug()
, we could continue to use this, but there is also a $faker->slug
method:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 10 - 26 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
$article->setTitle($this->faker->randomElement(self::$articleTitles)) | |
->setSlug($this->faker->slug) | |
// ... lines 32 - 60 | |
}); | |
// ... lines 62 - 63 | |
} | |
} |
The slug will now be totally different than the article title, but honestly, who cares?
For author, do the same thing: $this->faker->randomElement()
and pass self::$articleAuthors
:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 10 - 26 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 30 - 56 | |
$article->setAuthor($this->faker->randomElement(self::$articleAuthors)) | |
// ... lines 58 - 59 | |
; | |
}); | |
// ... lines 62 - 63 | |
} | |
} |
Copy that, and repeat it one more time for the imageFile
, this time using self::$articleImages
:
// ... lines 1 - 7 | |
class ArticleFixtures extends BaseFixture | |
{ | |
// ... lines 10 - 26 | |
public function loadData(ObjectManager $manager) | |
{ | |
$this->createMany(Article::class, 10, function(Article $article, $count) { | |
// ... lines 30 - 56 | |
$article->setAuthor($this->faker->randomElement(self::$articleAuthors)) | |
// ... line 58 | |
->setImageFilename($this->faker->randomElement(self::$articleImages)) | |
; | |
}); | |
// ... lines 62 - 63 | |
} | |
} |
Awesome! Let's go reload those fixtures!
php bin/console doctrine:fixtures:load
No errors! Find your browser and, try it! Oh, it's so much better.
If creating nice, random data seems like a small thing, it's not! Having rich data that you can easily load will increase your ability to create new features and fix bugs fast. It's totally worth it.
Next! Let's install a really cool library with automatic slugging super-powers.
Hello everyone, I have a problem and hope to find some help here.
In my CategoryFixture file, I declared an array type static variable with 5 elements for the "name" attribute of my category entity. Note that this attribute has a unique key constraint. I looped to do 5 records with faker's "randomElement ()" method, but when I run my command, a unique key violation exception is thrown. Considering my unique key constraint and my array of elements, is there a way to execute this fixture.
`
namespace App\DataFixtures;
use App\Entity\Category;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Faker\Factory;
class CategoryFixture extends Fixture
{
}
`