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.
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.
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.
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.
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.
Hey Diarill
What if you loop over the array of names instead? you'll get the same order everytime but I don't think that's a real problem
Cheers!
Is this al, system can be use also for a OneToOne relation ?
I'm not sure how to deal with this but I'm really interested to have your advices about it.
Thanks!
Hey Christina V.
The only problem with choosing a OneToOne relationship randomly is that it may assign the same object twice, you would have to remove it from the array after being selected.
Cheers!
hey @weaverryan it's been a while, how are things going?
I've got a question here regarding Faker and fixtures, I'm trying to create some fixtures for fake content (as its supposed to be), and I've got a couple of relationships between the tables, Author, Novel, Chapter (entities), 1 Author can have many Novel, 1 Novel can have many Chapter,
so in Author Entity:
/**
* @ORM\OneToMany(targetEntity="App\Entity\Novel", mappedBy="author")
*/
private $novels;
in Novel Entity:
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Author", inversedBy="novels")
* @ORM\JoinColumn(nullable=false)
*/
private $author;
in Chapter Entity:
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Novel", inversedBy="chapter")
* @ORM\JoinColumn(nullable=false)
*/
private $novel;
now whenever I try to fake a random number for the author id in the novel's fixture, i get an error telling me it must be an instance of Author
In Novel.php line 132:
<blockquote>
Argument 1 passed to App\Entity\Novel::setAuthor() must be an instance of App\Entity\Author or null,
int given, called in D:\vhosts\novelreader\src\DataFixtures\NovelFixtures.php on line 18
</blockquote>
The code for that particular field is $novel->setAuthor($this->faker->numberBetween($min = 1, $max = 10)); as I have only 10 authors with ids from 1 to 10
how can I get that working? as I see it happening when I create fixtures for the chapters of each novel all the same. I want to create 50 novels, and each novel should have X amount of Chaptes
thats not important though, the thing is, I'm certain I'll get the same issue, telling me that setNovel should be an instance of App\Entity\Novel or null.
think got it, using the refference that we added earlier in baseFixture.
$novel->setAuthor($this->getReference(Author::class.'_0'));
now gota figure out how to "randomize" that _0 so it loops through the list of authors, and assigns each novel to a random existing author.
Hey JLChafardet,
Well done! Glad you solved this yourself! Yeah, error message was pretty clear I suppose, you just need to convert that int number into a real entity, it sounds like you just miss something in your code.
Cheers!
Aha! This introduction explains the co-author of Fabien's first Symfony book. Francois was the documentation lead at the time.
Hey Edward B.!
You've got a great memory... or you've just been around Symfony for as LONG as I have ;). I first learned Symfony (version 1) because I was searching for a framework in PHP and Symfony had GREAT documentation. That was Francois' work - so I still owe him a lot for giving me such a great start :). Were you also a symfony 1 user?
Cheers!
For me it was Symfony 2 in 2013. My first non-proprietary PHP framework. Symfony's structure was completely non-intuitive to me, as was Doctrine's idea of writing the queries for me. So I grabbed the only book available, Fabien's. It's still on the shelf here! I'm just returning to Symfony from years of CakePHP, thus working through your (plural, the whole team's) tutorial sequences. Starting with an empty repo, trying to embrace the Symfony 5 way of doing things, is a challenge - and I'm trying to help out by leaving Symfony 5 notes as I go.
I just discovered "Object Design Style Guide" by Matthias Noback. It turns out to be a philosophy of how to write code in the Symfony 5 ecosystem. It carefully keeps that fact a secret which makes it fun! For example it explains when you should inject dependencies into the constructor of a service object, and when to inject the dependency into the method. A service, once constructed, should be immutable. So inject the logger into the constructor, but inject the User or Request object into the method, as they are specific to that method call, and the next call could be for a different user or request. (Noback's book is a Manning publication.)
Hey Edward B.!
> Symfony's structure was completely non-intuitive to me, as was Doctrine's idea of writing the queries for me
Yea, Symfony 2's structure was big and weird - I'm happy that it's much smaller now. And for someone with a lot of database experience, I *consistently* see that problem with Doctrine: it's super hard to think about classes & objects instead of tables and columns, especially with Doctrine relations :).
> Starting with an empty repo, trying to embrace the Symfony 5 way of doing things, is a challenge - and I'm trying to help out by leaving Symfony 5 notes as I go.
We appreciate that!
> I just discovered "Object Design Style Guide" by Matthias Noback. It turns out to be a philosophy of how to write code in the Symfony 5 ecosystem
Matthias is a SUPER smart and also pragmatic guy. I definitely recommend his stuff - good find!
Cheers!
Hello!
In new project I made ShoppingCartFixtures that extends BaseFixtures. But when I try to load fixtures I get error:
"An exception occurred while executing 'DELETE FROM order':
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "order"
LINE 1: DELETE FROM order"
I didn't make OrderFixtures yet, and ShopingCart does not contain any reference to Order entity.
I use postgresql 9.
Thanks in advance!
Hey Damir,
I think I know the problem! Looks like "order" is a reserved word in PostgreSql, so the query looks invalid for it. Try to rename your "order" table to something different, e.g. to "orders". I suppose the same might be needed for "user" table, because "user" might be a reserved word as well.
Cheers!
Hey Victor,
Thank u for replay.
Maybe you are right.
But, when I migrated entities, there were no problems with "order" table name.
I know, that in PostgreSQl 11 "order" word are reserved.
I already have added "shopping carts" by migration. I hope, when I will add "orders", it's won't be impossible.
Cheers!
Hey Damir,
I suppose it depends on queries, if you escape table and column names with back ticks - it should work even if you use reserved words. But better do not use reserved words at all, just simplify a lot of things :)
Cheers!
Hey Victor!
You are right. The problem was solved after changing "Order" to "TOrder".
Thanks a lot!
Hello. I finished to create my Fixtures and they are working well. Except for one little thing: They are not respecting a UniqueEntity constrain that I have in a couple of Entities. Do you know a way to run the fixtures with this kind of constrain? Just in case, I don't remember if when you were using Alice that was possible.
Hey Cesar,
For unique things I think you can try unique() modifier from Faker, see their docs for more info: https://github.com/fzaninotto/Faker#modifiers . As a workaround, you can manually add some unique indexes for generated strings to be extra sure it's unique, for example like:
for ($i = 0; $i < 10; $i++) {
$article = new Article();
// ...
$article->setSlug($this->faker->slug . '-' . $i)
}
I hope this helps!
Cheers!
Hello. I made BaseFixtures, and made to Classes extends this class.
But, when I call "php bin/console doctrine:fixtures:load", it execute only one fixture.
How can I load many fixtures, that extends BaseFixtures?
Hey Damir,
Glad you were able to fix it. I bet the problem was in missing implemented interface, so the system does not know that you have a few fixture files.
Cheers!
No. By mistake I deleted "extends BaseFixtures" in one of tow fixtures. Banal carelessness.
Thanks.
Hey Damir,
Ah, ok, sounds reasonable! :) Thank you for your feedback, might be useful for other users.
Cheers!
I've just realised they are static properties, thats why self was used. If they weren't static, could we use '$this->articleTitles'?
Is there any reason they're static?
Hey Cryptoblob,
Yes, exactly, that's because they are static. And sure, if you removed "static" keyword - you'd need to access them via "$this". I don't think there's a strong reason *why* exactly they are static. Mostly, you make properties or methods static if you want to call them without creating an object, i.e. call them in class context. But here, as I see, we call them in object context, so they might not be static. So I suppose it's just kinda personal convention, not sure :)
Cheers!
how do i make "setAuthor" to generate random entry from database ? or at least random generate with predefined id's ?
```
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Users", inversedBy="adverts")
* @ORM\JoinColumn(nullable=false)
*/
private $author;
```
I'm not sure if I understood your question correctly, but what you want is to create a new Author when calling setAuthor on a User object? If that's the case, that's not how it is supposed to work. You have to create a new Author instance, fulfill its data and then pass it to the setter method (And of course, calling persist & flush)
Cheers!
When i call php ./bin/console doctrine:fixtures:load
I get an error
Call to a member function text() on null
I've got the same error but i fixed it myself. The order is wrong:$this->manager = $manager;<br />$this->loadData($manager);<br />$this->faker = Factory::create();
loadData and Faker must be switched, otherwise it will use faker before it is created...$this->manager = $manager;<br />$this->faker = Factory::create();<br />$this->loadData($manager);
Hey Dion,
Thank you for this tip! Yeah, you need to create Faker instance before calling loadData() where you're going to use it.
I hope this will help Лёлик!
Cheers!
Hey Mrlelik!
Hmm. So, it sounds like $this->fake
is null for some reason. Make sure that your fixture class is extending the BaseFixture AND that your method is called loadData() instead of load() (otherwise, you will override the code that sets this property).
Let me know if this helps!
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.1.4
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.0.4
"symfony/console": "^4.0", // v4.0.14
"symfony/flex": "^1.0", // v1.17.6
"symfony/framework-bundle": "^4.0", // v4.0.14
"symfony/lts": "^4@dev", // dev-master
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/twig-bundle": "^4.0", // v4.0.4
"symfony/web-server-bundle": "^4.0", // v4.0.4
"symfony/yaml": "^4.0" // v4.0.14
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"fzaninotto/faker": "^1.7", // v1.7.1
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
"symfony/dotenv": "^4.0", // v4.0.14
"symfony/maker-bundle": "^1.0", // v1.4.0
"symfony/monolog-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.0.4
}
}
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
{
}
`