0
votes

I'm trying to create a set of fixtures for my new Symfony 4 project. I'm facing an issue, linked to some relations between my entities.

Here, you can find the concerned entities:

User

class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $username;

    /**
     * @var array
     *
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $firstname;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $insertion;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $lastname;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $email;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\UserGroup", inversedBy="users")
     * @ORM\JoinColumn(nullable=false)
     */
    private $userGroup;
}

UserGroup

class UserGroup
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $title;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\User", mappedBy="userGroup")
     */
    private $users;
}

I decided to create a BaseFixture class, with some methods. I found this idea on Symfonycast: https://symfonycasts.com/screencast/doctrine-relations/awesome-random-fixtures.

Here it is:

/**
 * Class BaseFixture
 * @package App\DataFixtures
 */
abstract class BaseFixture extends Fixture
{
    /** @var ObjectManager */
    private $manager;

    /** @var Generator */
    protected $faker;

    /**
     * @var array
     */
    private $referencesIndex = [];

    /**
     * @param ObjectManager $manager
     * @return mixed
     */
    abstract protected function loadData(ObjectManager $manager);

    /**
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        $this->manager = $manager;
        $this->faker = Factory::create();

        $this->loadData($manager);
    }

    /**
     * @param string $className
     * @param int $count
     * @param callable $factory
     */
    protected function createMany(string $className, int $count, callable $factory)
    {
        for ($i = 0; $i < $count; $i++) {

            $entity = new $className();
            $factory($entity, $i);

            $this->manager->persist($entity);
            $this->addReference($className . '_' . $i, $entity);
        }
    }

    /**
     * @param string $className
     * @return object
     * @throws Exception
     */
    protected function getRandomReference(string $className)
    {
        if (!isset($this->referencesIndex[$className])) {

            $this->referencesIndex[$className] = [];

            foreach ($this->referenceRepository->getReferences() as $key => $ref) {

                if (strpos($key, $className.'_') === 0) {
                    $this->referencesIndex[$className][] = $key;
                }
            }
        }

        if (empty($this->referencesIndex[$className])) {
            throw new Exception(sprintf('Cannot find any references for class "%s"', $className));
        }

        $randomReferenceKey = $this->faker->randomElement($this->referencesIndex[$className]);

        return $this->getReference($randomReferenceKey);
    }

    /**
     * @param string $className
     * @param int $count
     * @return array
     * @throws Exception
     */
    protected function getRandomReferences(string $className, int $count)
    {
        $references = [];

        while (count($references) < $count) {
            $references[] = $this->getRandomReference($className);
        }

        return $references;
    }
}

And after, I have my 2 Fixtures:

/**
 * Class UserGroupFixtures
 * @package App\DataFixtures
 */
class UserGroupFixtures extends Fixture
{
    /**
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {

        /** @var UserGroup $adminUserGroup */
        $adminUserGroup = new UserGroup();
        $adminUserGroup->setTitle('admin');

        $manager->persist($adminUserGroup);

        /** @var UserGroup $adminUserGroup */
        $userUserGroup = new UserGroup();
        $userUserGroup->setTitle('user');

        $manager->persist($userUserGroup);

        /** @var UserGroup $adminUserGroup */
        $viewerUserGroup = new UserGroup();
        $viewerUserGroup->setTitle('viewer');

        $manager->persist($viewerUserGroup);

        $manager->flush();
    }
}

And User Fixture:

class UserFixtures extends BaseFixture implements DependentFixtureInterface
{
    /**
     * @var array
     */
    private static $userRoles = [
        ["ROLE_ADMIN"],
        ["ROLE_USER"]
    ];

    /**
     * @var array
     */
    private static $userInsertion = [
        'van',
        'de',
        ''
    ];

    private $passwordEncoder;

    /**
     * UserFixtures constructor.
     * @param UserPasswordEncoderInterface $passwordEncoder
     */
    public function __construct(UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->passwordEncoder = $passwordEncoder;
    }

    /**
     * @param ObjectManager $manager
     */
    protected function loadData(ObjectManager $manager)
    {
        $this->createMany(User::class, 10, function(User $user) use ($manager) {

            $user->setUsername($this->faker->userName);
            $user->setRoles($this->faker->randomElement(self::$userRoles));
            $user->setPassword($this->passwordEncoder->encodePassword($user, 'ACTIVO'));
            $user->setFirstname($this->faker->firstName);
            $user->setLastname($this->faker->lastName);
            $user->setInsertion($this->faker->randomElement(self::$userInsertion));
            $user->setEmail($this->faker->email);
            $user->setDateCreated($this->faker->dateTime);
            $user->setDateUpdate($this->faker->dateTime);
            $user->setUnsubscribed($this->faker->boolean(15));
            $user->setDateUnsubscribed($this->faker->dateTime);

            $user->setUserGroup($this->getRandomReference(UserGroup::class));
        });

        $manager->flush();
    }

    /**
     * @return array|string
     */
    public function getDependencies()
    {
        return [UserGroupFixtures::class];
    }
}

As you can see, I use the function getDependencies() because the fixtures for UserGroup must exist before User.

When I launch the command to load the fixtures, UserGroupFixtures is called before UserGroup but.... I face an error, located in the BaseFixture file, at this line:

if (empty($this->referencesIndex[$className])) {
    throw new Exception(sprintf('Cannot find any references for class "%s"', $className));
}

Some help can be appreciate, since it's hard to debug fixtures ;)

1
The UserGroup entity instances don't seem to be added to the references anywhere? It would be helpful to include the url to the symfonycasts where this idea came from.ejuhjav
@ejuhjav I edit my original post, with the link from Symfonycast. Thx for your help ;)Chris_1985

1 Answers

1
votes

As commented initially, the issue looks to be that you are not storing the UserGroup instances in the references in the first fixture. You can try updating your UserGroupFixtures class to be the following:

/**
 * Class UserGroupFixtures
 * @package App\DataFixtures
 */
class UserGroupFixtures extends Fixture
{
    /**
     * @param ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        /** @var UserGroup $adminUserGroup */
        $adminUserGroup = new UserGroup();
        $adminUserGroup->setTitle('admin');

        $manager->persist($adminUserGroup);
        $this->addReference(UserGroup::class . '_1', $adminUserGroup);

        /** @var UserGroup $adminUserGroup */
        $userUserGroup = new UserGroup();
        $userUserGroup->setTitle('user');

        $manager->persist($userUserGroup);
        $this->addReference(UserGroup::class . '_2', $userUserGroup);

        /** @var UserGroup $adminUserGroup */
        $viewerUserGroup = new UserGroup();
        $viewerUserGroup->setTitle('viewer');

        $manager->persist($viewerUserGroup);
        $this->addReference(UserGroup::class . '_3', $viewerUserGroup);

        $manager->flush();
    }
}