zenstruck / foundry

A model factory library for creating expressive, auto-completable, on-demand dev/test fixtures with Symfony and Doctrine.
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html
MIT License
611 stars 63 forks source link

Migrating doctrine fixtures to foundry #468

Closed Lydnasty closed 1 year ago

Lydnasty commented 1 year ago

So I'm trying to migrate my fixtures from Doctrine to foundry to have a cleaner code, but the execution time is multiplied by 200 probably because it's flushing for every entity created.

This is my original code:

    public function load(ObjectManager $manager)
    {
        $time_start = microtime(true);
        foreach (FixtureConstants::USERS_REFERENCE_LIST as $userReference => $userIndex) {
            $user = new User();
            $user->setFirstName($this->faker->firstName);
            $user->setLastName($this->faker->lastName);
            $user->setEmail($userIndex['email']);
            $user->setPassword($this->passwordEncoder->encodePassword($user, FixtureConstants::USERS_PASSWORD));
            $user->setRoles([$userIndex['role']]);
            $user->setLastLoginAt(new \DateTime());
            $user->setCountry($this->getReference($userIndex['country']));
            $user->setCreatedAt($this->faker->dateTimeBetween('-2 weeks'));
            $manager->persist($user);
            $this->addReference($userReference, $user);
        }
        $manager->flush();
        dd((microtime(true) - $time_start)); // --- --- --- --- 0.044 seconds
    }

And this is the code I using foundry:

    public function load(ObjectManager $manager): void
    {
        $time_start = microtime(true);
        foreach (FixtureConstants::USERS_REFERENCE_LIST as $userReference => $userIndex) {
            $user = UserFactory::createOne([
                'email' => $userIndex['email'],
                'password' => '1234',
                'roles' => [$userIndex['role']],
            ]);
            $this->addReference($userReference, $user->object());
        }

        dd((microtime(true) - $time_start)); //  --- --- --- --- 10 seconds
    }

I've tried using Factory::delayFlush but I'm getting the same execution time, probably because I'm doing something wrong:

    public function load(ObjectManager $manager): void
    {
        $time_start = microtime(true);
        $users = UserFactory::delayFlush(function () {
            $users = [];
            foreach (FixtureConstants::USERS_REFERENCE_LIST as $userReference => $userIndex) {
                $users[$userReference] = UserFactory::new([
                    'email' => $userIndex['email'],
                    'password' => '1234',
                    'roles' => [$userIndex['role']],
                ])->withoutPersisting()->create();
            }
            return $users;
        });
        foreach ($users as $reference => $user) {
            $this->addReference($reference, $user->object());
        }

        dd((microtime(true) - $time_start)); // --- --- --- --- 10 seconds
    }

Can you tell me what I'm doing wrong please?

Lydnasty commented 1 year ago

I've found the issue.

The problem was this piece of code in my UserFactory.


    protected function initialize(): self
    {
        return $this->afterInstantiate(function (User $user) {
            $user->setPassword($this->passwordHasher->hashPassword($user, $user->getPassword()));
        });
    }

Removing it solved the performance problem. I'll have to find another way to do it.

nikophil commented 1 year ago

Hi @Lydnasty

I think you should change the password hasher algorithm used in your test

from https://symfony.com/doc/current/security/passwords.html#configuring-a-password-hasher :

Hashing passwords is resource intensive and takes time in order to generate secure password hashes. In general, this makes your password hashing more secure. In tests however, secure hashes are not important, so you can change the password hasher configuration in test environment to run tests faster:

kbond commented 1 year ago

flushing for every entity created.

https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#delay-flush can help here.