Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Foundry: Fixture Model Factories

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

In the load() method of the fixture class, we can create as much dummy data as we want. Right now... we're creating exactly one Question... which isn't making for a very realistic experience.

If we created more questions... and especially in the future when we will have multiple database tables that relate to each other, this class would start to get ugly. It's... already kind of ugly.

Hello Foundry!

No, we deserve better! Let's use a super fun new library instead. Google for "Zenstruck Foundry" and find its GitHub Page.

Foundry is all about creating Doctrine entity objects in an easy, repeatable way. It's perfect for fixtures as well as for functional tests where you want to seed your database with data at the start of each test. It even has extra features for test assertions!

The bundle was created by Kevin Bond - a long time Symfony contributor and friend of mine who's been creating some really excellent libraries lately. Foundry is Canadian for fun!

Installing Foundry

Let's get to work! Scroll down to the installation, copy the composer require line, find your terminal and paste. The --dev is here because we only need to load dummy data in the dev & test environments.

composer require zenstruck/foundry --dev

While that's running, head back to the docs. Let me show you what this bundle is all about. Suppose you have entities like Category or Post. The idea is that, for each entity, we will generate a corresponding model factory. So, a Post entity will have a PostFactory class, which will look something like this.

Once we have that, we can configure some default data for the entity class and then... start creating objects!

I know I explained that quickly, but that's because we're going to see this in action. Back at the terminal... let's wait for this to finish. I'm actually recording at my parents' house... where the Internet is barely a step up from dial-up.

After an edited break where I ate a sandwich and watched Moana, this finally finishes.

make:factory

Let's generate one of those fancy-looking model factories for Question. To do that, run:

symfony console make:factory

I also could have run bin/console make:factory... because this command doesn't need the database environment variables... but it's easier to get in the habit of always using symfony console.

Select Question from the list and... done! Go check out the new class src/Factory/QuestionFactory.php.

... lines 1 - 2
namespace App\Factory;
use App\Entity\Question;
use App\Repository\QuestionRepository;
use Zenstruck\Foundry\RepositoryProxy;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Proxy;
/**
* @method static Question|Proxy findOrCreate(array $attributes)
* @method static Question|Proxy random()
* @method static Question[]|Proxy[] randomSet(int $number)
* @method static Question[]|Proxy[] randomRange(int $min, int $max)
* @method static QuestionRepository|RepositoryProxy repository()
* @method Question|Proxy create($attributes = [])
* @method Question[]|Proxy[] createMany(int $number, $attributes = [])
*/
final class QuestionFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
// TODO add your default values here (https://github.com/zenstruck/foundry#model-factories)
];
}
protected function initialize(): self
{
// see https://github.com/zenstruck/foundry#initialization
return $this
// ->beforeInstantiate(function(Question $question) {})
;
}
protected static function getClass(): string
{
return Question::class;
}
}

Adding Default Values

The only method that we need to worry about right now is getDefaults(). The idea is that we'll return an array of all of the data needed to create a Question. For example, we can set a name key to our dummy question name - "Missing pants".

... lines 1 - 19
final class QuestionFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
'name' => 'Missing pants',
... lines 26 - 40
];
}
... lines 43 - 55
}

This works a bit like Twig. When Foundry sees the name key, it will call the setName() method on Question. Internally, this uses Symfony's property-access component, which I'm mentioning, because it also supports passing data through the constructor if you need that.

Copy the rest of the dummy code from our fixture class, delete it... and delete everything actually.

... lines 1 - 9
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
... lines 14 - 15
$manager->flush();
}
}

Back in QuestionFactory, paste!

But we need to convert all of this into array keys. As exciting as this is... I'll... type really fast.

... lines 1 - 19
final class QuestionFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
'name' => 'Missing pants',
'slug' => 'missing-pants-'.rand(0, 1000),
'question' => <<<EOF
Hi! So... I'm having a *weird* day. Yesterday, I cast a spell
to make my dishes wash themselves. But while I was casting it,
I slipped a little and I think `I also hit my pants with the spell`.
When I woke up this morning, I caught a quick glimpse of my pants
opening the front door and walking out! I've been out all afternoon
(with no pants mind you) searching for them.
Does anyone have a spell to call your pants back?
EOF
,
'askedAt' => rand(1, 10) > 2 ? new \DateTime(sprintf('-%d days', rand(1, 100))) : null,
'votes' => rand(-20, 50),
];
}
... lines 43 - 55
}

And.... done! Phew...

Using the Factory

Ok! We now have a simple array of "default" values that are enough to create a valid Question object. Our QuestionFactory is ready! Let's use it in AppFixtures.

How? First, say QuestionFactory::new(). That will give us a new instance of the QuestionFactory. Now ->create() to create a single Question.

... lines 1 - 5
use App\Factory\QuestionFactory;
... lines 7 - 9
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
QuestionFactory::new()->create();
$manager->flush();
}
}

Done! Ok, it's still not interesting - it will create just one Question... but let's try it! Re-run the fixtures:

symfony console doctrine:fixtures:load

Answer yes and... no errors! Head over to the browser, refresh and... oh! Zero questions! Ah, my one question is probably unpublished. Load the fixtures again:

symfony console doctrine:fixtures:load

Refresh and... there it is!

createMany()

At this point, you might be wondering: why is this better? Valid question. It's better because we've only just started to scratch the service of what Foundry can do. Want to create 20 questions instead of just one? Change create() to createMany(20).

Tip

In the latest version of Foundry, creating many objects is easier: just QuestionFactory::createMany(). You can also create a single object with the shorter QuestionFactory::createOne().

... lines 1 - 5
use App\Factory\QuestionFactory;
... lines 7 - 9
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
QuestionFactory::new()->createMany(20);
$manager->flush();
}
}

That's it. Reload the fixtures again:

symfony console doctrine:fixtures:load

Then go check out the homepage. Hello 20 questions created with one line of very readable code.

But wait there's more Foundry goodness! Foundry comes with built-in support for a library called faker. A handy tool for creating truly fake data. Let's improve the quality of our fake data and see a few other cool things that Foundry can do next.

Leave a comment!

28
Login or Register to join the conversation

For those who would read this and didn't get a new `make:factory` command.

I think the new recipes (v1.10+) are not compatible with Symfony 5.1.x.

Make sure you register the bundle manually.

# config/bundles.php

return [
// ...
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],
];

4 Reply

Hey Julien,

Thanks for sharing this workaround, it might be helpful! Though, Symfony Flex should register the bundle automatically nevertheless any changes in the recipe. Might it be so that you disallowed the Symfony Flex plugin execution when you were installing the bundle? Anyway, makins sure the bundle is automatically register is a good idea 👍

Cheers!

1 Reply
Samuel E. Avatar

Can you give more insight on how you would have disallowed the Symfony Flex Plugin? and making sure things are registering automatically? I also had to register the bundle manually for it to work.

Reply

Hey Samuel,

I would not suggest to disallow the bundle. To check - you can take a look at your composer.json file, in particular "config.allow-plugins" option. There should be "symfony/flex": true". If it's false, then the plugin is not allowed to run and so it won't ask you for installing new recipes and won't install them.

Also, please, make sure "allow-contrib" is set to "true" in "extra.symfony" option in case you're trying to install non-official recipe from symfony/recips-contrib repository.

If the config looks good in your composer.json - great, just try to reinstall the specific recipe with "symfony composer recipe:install" command. You may need to add --force and --reset flags if it does nothing without them.

I hope this helps.

Cheers!

1 Reply
Samuel E. Avatar

Thanks for the insight! The "allow-contrib" was false for me. Also I didn't have an "allow-plugins" key/value pair under config either. So I am thinking by default these weren't added.

Reply

Hey Samuel,

If allow-contrib is false and you're installing a bundle from the symfony/recipe-contrib repo - that's the problem. You should make it true, and remove that key completely so Composer will ask you the next time. If no allow-plugins key - great, then it will be asked by Composer the next time too, just look closer at the output to do not miss replaying "yes" there.

After you change the config - you can try to remove the package with "composer remove" and then re-add it with "composer require" commands and this time it should ask, and if you replied yes - it would install the recipe. Or as I said, you can just force re-installing the recipe yourself with "recipe:install" command

Cheers!

1 Reply
Sean Avatar

Hello again. I swear Ill make it through one of these without running into an issue.

Tonight suddenly started getting an error running "composer require zenstruck/foundry --dev", which is weird because I installed it a few days ago.....I dunno.

If anyone else is getting `Your requirements could not be resolved to an installable set of of packages.` and it references doctrine/persistence what fixed it for me was editing my composer.json, under "require-dev" add the line "doctrine/persistence": "1.3.8", . Run "composer update", then try installing package again.

Edit: thought you could do markdown...I was wrong

3 Reply

Hey Sean,

I just double-checked it: download the course code, goes to the start/ directory, require orm and zenstruck/foundry packs and it worked for me. So, not sure why exactly you hit that error, probably a new version was released that conflicted with your locked dependencies. Anyway, I'm glad you were able to get it working! And thank you for this tip - it might be useful to others.

Cheers!

Reply
Luca Avatar

Hey Sean and Victor,

ran into the same issue. Updating composer.json helped me out as well.

Reply

Hey Luca,

Great! What version of PHP do you have? Did you run the project from start/ directory or created a new project from scratch?

Cheers!

Reply
Luca Avatar

I'm running PHP Version 7.3.11 and I created a new project from scratch.

Reply

Hey Luca,

Thank you for sharing this info! We will try to investigate it.

Cheers!

Reply
Kevin B. Avatar

Hey all, this should be fixed in Foundry v1.1.2 (https://github.com/zenstruc... - I allow doctrine/persistence v2

Reply

Hey Kevin,

Awesome! Thank you for the quick fix :)

Cheers!

Reply

Thanks Kevin B.!

Yea, there is a "transition" happening right now due to some new Doctrine versions. About 2 weeks ago, DoctrineBundle started allowing doctrine/persistence 2, which means that new projects are now using that version. That means that if you try to install something that only allows v1, you get a dependency error... which is kind of hard to read in Composer. Btw, in Composer v2, the error message has been improved to help you understand this - https://github.com/composer...

Cheers!

Reply
Dang Avatar

Hello. In some previous tutorials, you presented us other library for the fixture called Alice Bundle. I wonder beacause this tuto is newer (Symfony 5) so this bundle zenstruck/foundry is better and I should use this instead for new project? Thanks

Reply

Hey Dang!

Yes, mo Foundry is much better than Alice. Foundry didn't exist when I wrote the earlier tutorials that used Alice. I now *much* prefer Foundry of Alice :). So use it instead!

Cheers!

Reply

We were loading the data through entity manager before and it looked simple and easy. Then , what's the need for these App fixtures?

Reply

Hey Brainiac,

Yes, Foundry definitely adds more overhead into your project than just simple fixtures, but it also provides more features out of the box. To know more about the features it brings - please, watch this screencast. Also, in the next video we cover some tricks, I'd also recommend you to watch. You can also find some explanation about Foundry on its GitHub repo page: https://github.com/zenstruc.... Or take a look at docs to see all benefits it can provide you if you require it in your project: https://symfony.com/bundles... .

But yeah, it's totally up you t use this or no, if you're happy with simple Doctrine fixtures and don't need all those Foundry features - great, just use them, it's totally valid :)

Cheers!

Reply
m3tal Avatar

Hi,

I would like to know, in big words, how I can create for example a test environment with separate docker. how I tell symfony to use other container for test environment.
thank you and have a nice day.

Reply

Hey Bogdan,

Unfortunately, we don't have any courses about Docker yet, but we will definitely want to release one in the future. For now, I can't tell you when it may happen, unfortunately. If you want a course about it - try to search on the internet, I bet there are a lot of examples you can reuse and modify for your needs. Also, I can recommend you to look at a few open PRs in Symfony Demo about adding Docker, I think you can stole some boilerplate code from there :)

- https://github.com/symfony/...
- https://github.com/symfony/...

I hope this helps!

Cheers!

1 Reply
Sherri Avatar

A few notes:

1) This way of calling createMany() is deprecated, I get a notice when calling doctrine:fixtures:load. The new way is like so:
QuestionFactory::createMany(20);
There is also a:
QuestionFactory::createOne();

2) The homepage twig template needs to be updated to show the proper votes count from the question object. The same as was done in the show template.

Reply

Hi Sherri!

Sorry for my very slow reply - but thanks for posting these!

> 1) This way of calling createMany() is deprecated

We'll get a note added about this so people are aware :) - thank you!

> 2) The homepage twig template needs to be updated to show the proper votes count from the question object. The same as was done in the show template.

You're right - I totally forgot about this piece.

Anyways, we always appreciate notes like these - we miss things, and sometimes things change - and we want to keep the tutorials as up-to-date as possible :).

Cheers!

Reply
Tim-K Avatar

Hey SymfonyCast-Team,

i have a Question of Concept:

I have already implemented classes in a folder called "Factory". I use these classes to help me in the regular use (env="prod") to create new objects and to store them in the database.

1) Is my understanding of "Factory" maybe wrong? (or how are such helper-functions called?)

2) Since I now want to use FOUNDRY, I run into trouble, because my "PersonFactory.php" already exists in the "Factory"-folder. But if I understand the concept of FOUNDRY correct these classes will never run on production (class extentions like 'ModelFactory' will not be available, as the package will not be installed on production ('composer require zenstruck/foundry *--dev*)) and so a merge of my current functions into the Foundry-Factories makes no sense.

Is it possible to change in the standard folder-name for FOUNDRY? (using the maker from the console)?

Or am I not working according convention with my current concept of FACTORY? If I need to change this it will be a big refactoring work...

Cheers
Tim

Reply

Hey Tim K.

> 1) Is my understanding of "Factory" maybe wrong? (or how are such helper-functions called?)
Usually a factory it's meant to create objects not persistin them in the database or any other storing mechanism

About your second question. Yes, Foundry will not be installed in your production server because it's a dev dependency, so you should not depend on it but in your tests or data fixtures. If you're having problems with classes using the same name, it can be easily fixed by using a different namespace for your classes.

Cheers!

Reply
Jonas ernesto P. Avatar
Jonas ernesto P. Avatar Jonas ernesto P. | posted 2 years ago

I have an error...
For me, Command "make:factory" is not defined.

Reply

Hey Jonas ernesto P.

I guess you have outdated MakerBundle! But wait, how did you get here? This course it not released yet! ;)

Cheers!

1 Reply
Jonas ernesto P. Avatar
Jonas ernesto P. Avatar Jonas ernesto P. | sadikoff | posted 2 years ago

The videos are not yet, but the text is!
See you!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^2.1", // 2.1.1
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.7", // 2.8.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "sentry/sentry-symfony": "^4.0", // 4.0.3
        "stof/doctrine-extensions-bundle": "^1.4", // v1.5.0
        "symfony/asset": "5.1.*", // v5.1.2
        "symfony/console": "5.1.*", // v5.1.2
        "symfony/dotenv": "5.1.*", // v5.1.2
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.1.*", // v5.1.2
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/stopwatch": "5.1.*", // v5.1.2
        "symfony/twig-bundle": "5.1.*", // v5.1.2
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.1.*", // v5.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.0.4
        "twig/twig": "^2.12|^3.0" // v3.0.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.1.*", // v5.1.2
        "symfony/maker-bundle": "^1.15", // v1.23.0
        "symfony/var-dumper": "5.1.*", // v5.1.2
        "symfony/web-profiler-bundle": "5.1.*", // v5.1.2
        "zenstruck/foundry": "^1.1" // v1.5.0
    }
}