gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Building fixtures is pretty simple, but kind of boring. And it would be super boring to manually create 25 mixes inside the load()
method. That's why we're going to install an awesome library called "Foundry". To do that, run:
composer require zenstruck/foundry --dev
We're using --dev
because we only need this tool when we're developing or running tests. When this finishes, run
git status
to see that the recipe enabled a bundle and also created one config file... which we won't need to look at.
In short, Foundry helps us create entity objects. It's... almost easier just to see it in action. First, for each entity in your project (right now, we only have one), you'll need a corresponding factory class. Create that by running
php bin/console make:factory
which is a Maker command that comes from Foundry. Then, you can select which entity you want to create a factory for... or generate a factory for all your entities. We'll generate one for VinylMix
. And... that created a single file: VinylMixFactory.php
. Let's go check it out: src/Factory/VinylMixFactory.php
.
... lines 1 - 10 | |
/** | |
* @extends ModelFactory<VinylMix> | |
* | |
* @method static VinylMix|Proxy createOne(array $attributes = []) | |
* @method static VinylMix[]|Proxy[] createMany(int $number, array|callable $attributes = []) | |
... lines 16 - 27 | |
*/ | |
final class VinylMixFactory extends ModelFactory | |
{ | |
... lines 31 - 37 | |
protected function getDefaults(): array | |
{ | |
return [ | |
// TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories) | |
'title' => self::faker()->text(), | |
'trackCount' => self::faker()->randomNumber(), | |
'genre' => self::faker()->text(), | |
'votes' => self::faker()->randomNumber(), | |
'slug' => self::faker()->text(), | |
'createdAt' => null, // TODO add DATETIME ORM type manually | |
'updatedAt' => null, // TODO add DATETIME ORM type manually | |
]; | |
} | |
... lines 51 - 63 | |
} |
Cool! Above the class, you can see a bunch of methods being described... which will help our editor know what super-powers this has. This factory is really good at creating and saving VinylMix
objects... or creating many of them, or finding a random one, or a random set, or a random range. Phew!
The only important code that we see inside this class is getDefaults()
, which returns default data that should be used for each property when a VinylMix
is created. We'll talk more about that in a minute.
But first... let's run blindly forward and use this class! In AppFixtures
, delete everything and replace it with VinylMixFactory::createOne()
.
... lines 1 - 5 | |
use App\Factory\VinylMixFactory; | |
... lines 7 - 9 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
VinylMixFactory::createOne(); | |
$manager->flush(); | |
} | |
} |
That's it! Spin over and reload the fixtures with:
symfony console doctrine:fixtures:load
And... it fails! Boo
Expected argument type "DateTime", "null" given at property path "createdAt"
It's telling us that something tried to call setCreatedAt()
on VinylMix
... but instead of passing a DateTime
object, it passed null
. Hmm. Inside of VinylMix
, if you scroll up and open TimestampableEntity
, yup! We have a setCreatedAt()
method that expects a DateTime
object. Something called this... but passed null
.
This actually helps show off how Foundry works. When we call VinylMixFactory::createOne()
, it creates a new VinylMix
and then sets all of this data onto it. But remember, all of these properties are private. So it doesn't set the title property directly. Instead, it calls setTitle()
and setTrackCount()
Down here, for createdAt
and updatedAt
, it called setCreatedAt()
and passed it null
.
In reality, we don't need to set these two properties because they will be set automatically by the timestampable behavior.
If we try this now...
symfony console doctrine:fixtures:load
It works! And if we go check out our site... awesome. This mix has 928,000 tracks, a random title, and 301 votes. All of this is coming from the getDefaults()
method.
To generate interesting data, Foundry leverages another library called "Faker", whose only job is to... create fake data. So if you want some fake text, you can say self::faker()->
, followed by whatever you want to generate. There are many different methods you can call on faker()
to get all kinds of fun fake data. Super handy!
Our factory did a pretty good job... but let's customize things to make it a bit more realistic. Actually, first, having one VinylMix
still isn't very useful. So instead, inside AppFixtures
, change this to createMany(25)
.
... lines 1 - 11 | |
public function load(ObjectManager $manager): void | |
{ | |
VinylMixFactory::createMany(25); | |
... lines 15 - 16 | |
} | |
... lines 18 - 19 |
This is where Foundry shines. If we reload our fixtures now:
symfony console doctrine:fixtures:load
With a single line of code, we have 25 random fixtures to work with! Though, the random data could be a bit better... so let's improve that.
Inside VinylMixFactory
, change the title. Instead of text()
- which can sometimes be a wall of text, change to words()
... and let's use 5 words, and pass true so it returns this as a string. Otherwise, the words()
method returns an array. For trackCount
, we do want a random number, but... probably a number between 5 and 20. For genre
, let's go for a randomElement()
to randomly choose either pop
or rock
. Those are the two genres that we've been working with so far. And, whoops... make sure you call this like a function. There we go. Finally, for votes
, choose a random number between -50 and 50.
... lines 1 - 28 | |
final class VinylMixFactory extends ModelFactory | |
{ | |
... lines 31 - 37 | |
protected function getDefaults(): array | |
{ | |
return [ | |
'title' => self::faker()->words(5, true), | |
'trackCount' => self::faker()->numberBetween(5, 20), | |
'genre' => self::faker()->randomElement(['pop', 'rock']), | |
'votes' => self::faker()->numberBetween(-50, 50), | |
'slug' => self::faker()->text(), | |
]; | |
} | |
... lines 48 - 60 | |
} |
Much better! Oh, and you can see that make:factory
added a bunch of our properties here by default, but it didn't add all of them. One that's missing is description
. Add it: 'description' => self::faker()->
and then use paragraph()
. Finally, for slug
, we don't need that at all because it will be set automatically.
... lines 1 - 37 | |
protected function getDefaults(): array | |
{ | |
return [ | |
... line 41 | |
'description' => self::faker()->paragraph(), | |
... lines 43 - 45 | |
]; | |
} | |
... lines 48 - 62 |
Phew! Let's try this! Reload the fixtures:
symfony console doctrine:fixtures:load
Then head over and refresh. That looks so much better. We do have one broken image... but that's just because the API I'm using has some "gaps" in it... nothing to worry about.
Foundry can do a ton of other cool things, so definitely check out its docs. It's especially useful when writing tests, and it works great with database relations. So we'll see it again in a more complex way in the next tutorial.
Next, let's add pagination! Because eventually, we won't be able to list every mix in our database all at once.
Hey @Georg-L
That's unexpected. Did you add the Slug attribute to your property? #[Slug(fields: ['title'])]
like shown in this code block https://symfonycasts.com/screencast/symfony-doctrine/sluggable#codeblock-1fd8df0329
Cheers!
Hey @MolloKhan,
thank you for your message. Me stupid! I managed to somehow delete this line and didn't notice.
sorry for bothering you.
Have a nice day
Hey, great library. I need a bit of help with localization. If I want another language, how to do this? In the documentation I see
Faker\Factory::create('fr_FR');
But how to set this in Symfony? Thank you!
Hey Rufnex,
Good question! If you're talking about how to localize Faker with the Foundry bundle - here's an example: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#faker . As you can see from the docs, you can do it via Symfony configuration:
# config/packages/zenstruck_foundry.yaml
when@dev: # see Bundle Configuration section about sharing this in the test environment
zenstruck_foundry:
faker:
locale: fr_FR # set the locale
I hope this helps!
Cheers!
Hi Victor,
i had already tried this. unfortunately, only the standard language is used. Any further ideas?
Greetings
Rufnex
Hey Rufnex,
Hm, could you clear the cache and try again? It might be a cache issue. If not, please, make sure your current config really has that locally, you can debug it with:
bin/console debug:config zenstruck_foundry
Is the locale set there? Also, make sure you're debugging this config for the environment that your Symfony application is currently using. It might be so that you set the locale for dev but load your app in prod.
Cheers!
HeyHo Victor,
the configuration looks correct, but still only the default language like "et accusamus sunt ipsum non". strange behavioer .. or is it required to download somewhere the language packs?
Current configuration for extension with alias "zenstruck_foundry"
==================================================================
zenstruck_foundry:
auto_refresh_proxies: true
faker:
locale: fr_FR
seed: null
service: null
instantiator:
without_constructor: false
allow_extra_attributes: false
always_force_properties: false
service: null
```
Hey Rufnex,
Ah, ok, it seems correct to me. Well, you don't need to install any extra localization pack, it's implemented via so called Provider in the faker bundle: https://github.com/FakerPHP/Faker/tree/main/src/Faker/Provider - you just need to specify a correct provider for the "locale" option I suppose. OK, could you try that Faker\Factory::create('fr_FR')
instead as described in the docs and try to generate data with it? Are those data really localized? If so, it sounds like a misconfiguration or a bug in the Foundry bundle, I'd recommend you to open an issue there.
Btw, keep in mind that not all the data might be translated. Please, try on the specific methods like name()
, region()
, vat()
, or phoneNumber()
as it's listed as translated in the docs: https://fakerphp.github.io/locales/fr_FR/ . It might be so that the method you're calling always returns Latin text.
I hope this helps!
Cheers!
Sorry, can you explain me, where to add Faker\Factory::create('fr_FR')
? i'm a bit confused ;)
I think Victor talk about something like this:
protected function getDefaults(): array
{
$faker = Factory::create('fr_FR');
return [
'title' => $faker->lastName(),
// 'title' => self::faker()->words(5, true),
'description' => self::faker()->paragraph(),
'trackCount' => self::faker()->numberBetween(5, 20),
'genre' => self::faker()->randomElement(['pop', 'rock']),
'votes' => self::faker()->numberBetween(-50, 50),
];
}
Hey Rufnex,
Hm, that's weird if even with the Faker\Factory::create('fr_FR')
you still get latin data. What method are you. calling on that custom localized faker? Could you show a part of your code where you're using it? Because as far as I understand your code it should just work this way, probably you're missing something obvious.
Cheers!
Hey Rufnex,
Yep, exactly like Ruslan mentioned below in https://symfonycasts.com/screencast/symfony-doctrine/foundry#comment-27727
I.e. you create a faker instance manually via $faker = Factory::create('fr_FR');
passing the desired locale and then use it below.
Cheers!
I followed the example .. so in my factory i have this code:
protected function getDefaults(): array
{
$faker = Factory::create('fr_FR');
return [
// TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
'title' => $faker->words(5, true),
'description' => $faker->text(),
'hits' => self::faker()->randomNumber(),
];
}
Hey Rufnex,
Oh, that's exactly what is in the docs actually, I'm not sure why it does not work. Well, the only explanation might be is that those methods you're using like words()
/text()
are not localized to French. I'd recommend you to change that code to something like this:
protected function getDefaults(): array
{
$faker = Factory::create('fr_FR');
// This should dump a French region and a French phone number that is started with +33
dd($faker->region(), $faker->phoneNumber());
return [
// TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
'title' => $faker->words(5, true),
'description' => $faker->text(),
'hits' => self::faker()->randomNumber(),
];
}
And re-load your fixtures. In the console, you should get a dumped French region and a French number that starts with +33 - it you see it - then localization actually work, but it would mean as I said before that those method you're using like text()
are not localized to French.
But if you don't see a french region and french phone number dumped - than it might be a bug in the Faker library you have installed. I'd recommend you to upgrade to the latest version and double-check if it works there. if not - probably report a bug in the Faker repository. Though, it would be a very obvious bug I suppose so other devs had to notice it already. I'm leaning towards the fact that the methods you're using are just not localizaed.
Cheers!
Hi Victor,
I think I have found the problem. The formatters text()
and word()
can't seem to be localized. To create a local text you can use realText()
$faker->realText()
$faker->realText('30')
It seems that the translation only works for the following: https://fakerphp.github.io/formatters/
If someone has the same problem, the insight might be helpful. And yes RTFM ;o)
Anyway, with the translated results, I think I'd rather stay with Latin LOL.
Thanks for your help, your service is great!
Hey Rufnex,
That's exactly what I was trying to explain you a few times :) Yeah, not everything is localized, unfortunately... or fortunately, because it might be done on purpose, that's just a strategy of that lib.
Good catch on that realText()
method, and thanks for sharing it with others!
Anyway, with the translated results, I think I'd rather stay with Latin LOL
Lol! But yeah, that's the reason why the "lorem ipsum" is so popular, it has no "semantic load", just text :)
Anyway, I'm really happy we figured this mystery out! ;)
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.12", // 2.12.3
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
"pagerfanta/twig": "^3.6", // v3.6.1
"sensio/framework-extra-bundle": "^6.2", // v6.2.6
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/asset": "6.1.*", // v6.1.0
"symfony/console": "6.1.*", // v6.1.2
"symfony/dotenv": "6.1.*", // v6.1.0
"symfony/flex": "^2", // v2.2.2
"symfony/framework-bundle": "6.1.*", // v6.1.2
"symfony/http-client": "6.1.*", // v6.1.2
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
"symfony/runtime": "6.1.*", // v6.1.1
"symfony/twig-bundle": "6.1.*", // v6.1.1
"symfony/ux-turbo": "^2.0", // v2.3.0
"symfony/webpack-encore-bundle": "^1.13", // v1.15.1
"symfony/yaml": "6.1.*", // v6.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "6.1.*", // v6.1.0
"symfony/maker-bundle": "^1.41", // v1.44.0
"symfony/stopwatch": "6.1.*", // v6.1.0
"symfony/web-profiler-bundle": "6.1.*", // v6.1.2
"zenstruck/foundry": "^1.21" // v1.21.0
}
}
Hey team,
I just followed the tutorial and got an Error. To blame me: I am using symfony 6.2.8.
But did I miss something? The new Method from MixController works fine and slug is created automatically, but deleting 'slug' => self::faker()->text() and executing "symfony console doctrine:fixtures:load" leads to
In ExceptionConverter.php line 47:
An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "slug" of relation "vinyl_mix" violates not-null constraint
DETAIL: Failing row contains (9, id veniam ducimus exercitationem est, Totam dolor et tenetur fuga voluptas nisi rerum. Est animi corpo..., 6, rock, 2023-04-17 17:21:34, 20, 2023-04-17 17:21:34, null).
Any comment is appreciated.
Thank you.