7
votes

I'm asking if it's possible to pass an array as value for some elements? For example in my case I'm trying to set roles for a FOSUserBundle User entity that takes roles as an array of values and not plain values. I have this in my fixture:

UserBundle\Entity\User:
    User0:
        username: admin
        email: [email protected]
        enabled: 1
        plainPassword: admin
        roles: [ROLE_ADMIN]
        groups: @Group0

    User{1..10}:
        username: <firstNameMale>
        email: <companyEmail>
        enabled: <boolean(35)>
        plainPassword: <lexify>
        roles: 35%? [ROLE_ADMIN, ROLE_USER, ROLE_PROFILE_ONE, ROLE_PROFILE_TWO]
        groups: @Group*

But it's not working and I'm getting this error:

[Symfony\Component\Debug\Exception\ContextErrorException] Catchable Fatal Error: Argument 1 passed to FOS\UserBundle\Model\User::setRoles() must be of the type array, string given, called in /var/www/html/vendor/nelmio/alice/src/Nelmio/Alice/Loader/Base.php on line 483 and defined in /var/www/html/vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Model/User.php line 530

Any advice around this?

Update answer

Using first approach with plain array in YAML file:

After made some changes as @frumious suggested the fixture now has this content:

UserBundle\Entity\User:
    User0:
        username: admin
        email: [email protected]
        enabled: 1
        plainPassword: admin
        roles: [ROLE_ADMIN]
        groups: @Group0

    User{1..10}:
        username: <firstNameMale>
        email: <companyEmail>
        enabled: <boolean(35)>
        plainPassword: <lexify>
        roles: [ROLE_PROFILE_ONE, ROLE_PROFILE_TWO]
        groups: @Group*

In this way I will assign always the two roles for each test User but I'm having some problems trying to get where the Faker should be placed and which code to write inside it.

But any time I try to execute the set by calling:

h4cc_alice_fixtures:load:sets ./src/CommonBundle/DataFixtures/TananeSet.php

I got this error:

[ErrorException] Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php o
n line 555 and defined in /var/www/html/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php line 47

Which makes me think the problem here is related to $groups variable in User entity. This is a piece of code on that entity:

/**
 * @ORM\Entity
 * @ORM\Table(name="fos_user")
 * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
 * @ORM\Entity(repositoryClass="UserBundle\Entity\Repository\UserRepository")
 */
class User extends BaseUser {
    /**
     * Hook timestampable behavior
     * updates createdAt, updatedAt fields
     */
    use TimestampableEntity;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Group")
     * @ORM\JoinTable(name="fos_user_user_group",
     *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")}
     * )
     */
    protected $groups;

    /**
     * @ORM\Column(name="deletedAt", type="datetime", nullable=true)
     */
    protected $deletedAt;

}

How I can fix that error? What I should pass as parameter for groups?

Using second approach: defining a service

Following the other suggestion by @frumious I define a service as follow:

services:
    roles.faker.provider:
        class: CommonBundle\Tools\RolesFakerProvider
        tags:
            -  { name: h4cc_alice_fixtures.provider }

And this is the method:

namespace CommonBundle\Tools;

class RolesFakerProvider {

    public function randomRoles()
    {
        $names = ['ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO'];

        return [$names[array_rand($names)]];
    }

}

Then I made this changes:

UserBundle\Entity\User:
    User0:
        username: admin
        email: [email protected]
        enabled: 1
        plainPassword: admin
        roles: [ROLE_ADMIN]
        groups: @Group0

    User{1..10}:
        username: <firstNameMale>
        email: <companyEmail>
        enabled: <boolean(35)>
        plainPassword: <lexify>
        # BEFORE
        #roles: [ROLE_PROFILE_ONE, ROLE_PROFILE_TWO]
        # AFTER
        roles: <randomRoles>
        groups: @Group*

And this one is returning this error instead:

[Symfony\Component\Debug\Exception\ContextErrorException] Catchable Fatal Error: Argument 1 passed to FOS\UserBundle\Model\User::setRoles() must be of the type array, string given, called in /var/www/html/vendor/nelmio/alice/src/Nelmio/Alice/Loader/Base.php on line 483 and defin ed in /var/www/html/vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Model/User.php line 530

Which makes me think the function isn't returning an array or something else is getting wrong, any advice around this one too?

3
This is surprisingly difficult, isn't it?! Approach 1: can you just put @Group* in square brackets, groups: [@Group*]? Approach 2: that looks like it should work to me, if your service is getting injected properly, and the method is actually being called (the method, as defined, clearly returns an array!). I'd try it in-line in the YAML (roles: <?php ?> etc), and also with [] (roles: [<?php ?>]). If that works and the service doesn't, I'd debug/log/die() to try to work out what's actually happening!frumious
Actually, I wonder if the problem is that whatever you return in your custom method still has to be text that is interpreted by Alice (I had assumed that using this technique you could return any instance of a class and that would get used by Alice, but now I'm not so sure). If that's true, then even returning a PHP Array would, from Alice's perspective, result in a single (meaningless) text string. So try putting the method call in [] (roles: [<randomRole>]) or returning the [] from the method (return '['.$names[array_rand($names)].']';)frumious

3 Answers

6
votes

Essentially just a guess based on a quick look at the docs, but I suspect the problem may be that in roles: 35%? [ROLE_ADMIN, ROLE_USER, ROLE_PROFILE_ONE, ROLE_PROFILE_TWO] the bit after roles: is being interpreted as a single string, because it doesn't start with [ as a normal YAML array would need to.

As for a solution, I suspect that you can't do it straight in the YAML like that.

One (not proven) option: use a custom Faker method:

Faker

public function roles()
{
    return = ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO'];
}

YAML

User{1..10}:
    username: <firstNameMale>
    email: <companyEmail>
    enabled: <boolean(35)>
    plainPassword: <lexify>
    roles: 35%? <roles()>
    groups: @Group*

Final query: do you really want Alice to assign all those Roles to the User 35% of the time? If not, and in fact you want some probability-based choice of one of them in each User, then I suppose what you need is still a custom method, but put the selection logic in there instead of in the YAML.

EDIT

Ah, sounds like you want random single Roles for each test instance, in which case you'll need custom code something like this:

public function randomRole()
{
    $names = ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO'];

    return $names[array_rand($names)];
}

According to Alice it looks like you can stick that straight in the YAML like this:

User{1..10}:
    username: <firstNameMale>
    email: <companyEmail>
    enabled: <boolean(35)>
    plainPassword: <lexify>
    roles: <?php $names = ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO']; echo $names[array_rand($names)]; ?>
    groups: @Group*

Or the AliceFixturesBundle docs tell you how to include a separate Provider (as described above)

services.yml

services:
    your.faker.provider:
        class: YourProviderClass
        tags:
            -  { name: h4cc_alice_fixtures.provider }


This suggestion doesn't work, keeping for posterity but moved down the bottom!

I thought maybe you can do it be defining the array separately at the top and then referring to it, using Alice Value Objects, but since an array is not a normal object I can't see how to instantiate it. You'd want something like this:

Array:
    Array0: [ROLE_ADMIN, ROLE_USER, ROLE_PROFILE_ONE, ROLE_PROFILE_TWO]

UserBundle\Entity\User:
    User0:
        username: admin
        email: [email protected]
        enabled: 1
        plainPassword: admin
        roles: [ROLE_ADMIN]
        groups: @Group0

    User{1..10}:
        username: <firstNameMale>
        email: <companyEmail>
        enabled: <boolean(35)>
        plainPassword: <lexify>
        roles: 35%? @Array0
        groups: @Group*
1
votes

The problem I believe is that you need to set an array of roles, so you can't just return one role. Either do this in-line:

User{1..10}:
    username: <firstNameMale>
    email: <companyEmail>
    enabled: <boolean(35)>
    plainPassword: <lexify>
    roles: <?php $names = ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO']; echo '['.$names[array_rand($names)].']'; ?>
    groups: @Group*

Another issue could be that when you give an array to something it may unpack it as a list of arg. Try to pass it [ ['Foo'] ] i.e. an array as the first arg of the other array. Either way once you figure it out I think you should send a pull request for the docs or file an issue at least because this probably shouldn't be so complicated.

0
votes

Try this one

roles: <randomElements(['ROLE_ADMIN', 'ROLE_USER', 'ROLE_PROFILE_ONE', 'ROLE_PROFILE_TWO'], 2)>

randomElements args:
1- array 'Array to take elements from. Defaults to a-f'.
2- integer 'Number of elements to take'.
3- boolean 'Allow elements to be picked several times. Defaults to false'.

It will return array.