title: Symfony 4 - load fixtures for dynamic user roles
body: <p>Currently I'm trying to modify my classes and looking for idea to save dynamic relations between users and roles.</p>
  
  <p>I want to create associations when loading fixtures and also to have such a functionality in controller when I need to create an user with relation, example:</p>
  
  <pre><code>...
  $user = new User();
  $user-&gt;setName($_POST['name']);
  $user-&gt;setPassword($_POST['password']);
  ...
  $user-&gt;setRole('ROLE_USER');//Role for everyone
  ...
  $role = new Role();
  $role-&gt;setName('ROLE_' . strtoupper($_POST['name']) );//Role for personal use
  ...
  //Here need to implement user+role association (I'm looking for recommendations)
  ...
  $entityManager-&gt;persist($user);
  $entityManager-&gt;persist($role);
  //Persist role+user assosiacion
  $entityManager-&gt;flush();
  $entityManager-&gt;clear();
  </code></pre>
  
  <p>My User.php :</p>
  
  <pre><code>    &lt;?php
  
      namespace App\Entity;
  
      use DateTime;
      use Doctrine\Common\Collections\ArrayCollection;
      use Doctrine\ORM\Mapping as ORM;
      use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
      use Symfony\Component\Security\Core\User\UserInterface;
  
      /**
       * User
       *
       * @ORM\Table(name="user", uniqueConstraints={@ORM\UniqueConstraint(name="user_name", columns={"user_name"}), @ORM\UniqueConstraint(name="email", columns={"email"})})
       * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
       * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="fast_cache")
       * @UniqueEntity(fields="email", message="Email already taken")
       * @UniqueEntity(fields="username", message="Username already taken")
       */
      class User implements UserInterface, \Serializable
      {
          /**
           * @var ArrayCollection
           *
           * @ORM\ManyToMany(targetEntity="App\Entity\Role", inversedBy="users", cascade={"remove"})
           * @ORM\JoinTable(name="users_roles",
           *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
           *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
           *      )
           */
          protected $roles;
          /**
           * @var int
           *
           * @ORM\Column(name="id", type="smallint", nullable=false, options={"unsigned"=true})
           * @ORM\Id
           * @ORM\GeneratedValue(strategy="AUTO")
           */
          private $id;
          /**
           * @var string
           *
           * @ORM\Column(name="user_name", type="string", length=255, nullable=false)
           */
          private $username;
          /**
           * @var string
           *
           * @ORM\Column(name="email", type="string", length=255, nullable=false)
           */
          private $email;
          /**
           * @var string
           *
           * @ORM\Column(name="password", type="string", length=255, nullable=false)
           */
          private $password;
          /**
           * @var bool
           *
           * @ORM\Column(name="is_enabled", type="boolean", nullable=false)
           */
          private $isEnabled;
          /**
           * @var bool
           *
           * @ORM\Column(name="is_verified", type="boolean", nullable=false)
           */
          private $isVerified;
          /**
           * @var DateTime
           *
           * @ORM\Column(name="created_at", type="datetime", nullable=false)
           */
          private $createdAt;
          /**
           * @var DateTime
           *
           * @ORM\Column(name="updated_at", type="datetime", nullable=false)
           */
          private $updatedAt;
  
          /**
           * @return int
           */
          public function getId(): int
          {
              return $this-&gt;id;
          }
  
          /**
           * @return string
           */
          public function getEmail(): string
          {
              return $this-&gt;email;
          }
  
          /**
           * @param string $email
           */
          public function setEmail(string $email): void
          {
              $this-&gt;email = $email;
          }
  
          /**
           * @return bool
           */
          public function isEnabled(): bool
          {
              return $this-&gt;isEnabled;
          }
  
          /**
           * @param bool $isEnabled
           */
          public function setIsEnabled(bool $isEnabled): void
          {
              $this-&gt;isEnabled = $isEnabled;
          }
  
          /**
           * @return bool
           */
          public function isVerified(): bool
          {
              return $this-&gt;isVerified;
          }
  
          /**
           * @param bool $isVerified
           */
          public function setIsVerified(bool $isVerified): void
          {
              $this-&gt;isVerified = $isVerified;
          }
  
          /**
           * @return DateTime
           */
          public function getCreatedAt(): DateTime
          {
              return $this-&gt;createdAt;
          }
  
          /**
           * @param DateTime $createdAt
           */
          public function setCreatedAt(DateTime $createdAt): void
          {
              $this-&gt;createdAt = $createdAt;
          }
  
          /**
           * @return DateTime
           */
          public function getUpdatedAt(): DateTime
          {
              return $this-&gt;updatedAt;
          }
  
          /**
           * @param DateTime $updatedAt
           */
          public function setUpdatedAt(DateTime $updatedAt): void
          {
              $this-&gt;updatedAt = $updatedAt;
          }
  
          /**
           * String representation of object
           * @link http://php.net/manual/en/serializable.serialize.php
           * @return string the string representation of the object or null
           * @since 5.1.0
           * NOTE: SYNFONY BUG 3.4 -&gt; 4.1; https://github.com/symfony/symfony-docs/pull/9914
           */
          public function serialize(): string
          {
              // add $this-&gt;salt too if you don't use Bcrypt or Argon2i
              return serialize([$this-&gt;id, $this-&gt;username, $this-&gt;password]);
          }
  
          /**
           * Constructs the object
           * @link http://php.net/manual/en/serializable.unserialize.php
           * @param string $serialized &lt;p&gt;
           * The string representation of the object.
           * &lt;/p&gt;
           * @return void
           * @since 5.1.0
           */
          public function unserialize($serialized): void
          {
              // add $this-&gt;salt too if you don't use Bcrypt or Argon2i
              [$this-&gt;id, $this-&gt;username, $this-&gt;password] = unserialize($serialized, ['allowed_classes' =&gt; false]);
          }
  
          /**
           * Returns the roles granted to the user.
           *
           * &lt;code&gt;
           * public function getRoles()
           * {
           *     return array('ROLE_USER');
           * }
           * &lt;/code&gt;
           *
           * Alternatively, the roles might be stored on a ``roles`` property,
           * and populated in any number of different ways when the user object
           * is created.
           *
           * @return array The user roles
           */
          public function getRoles(): array
          {
              $roles = [];
              foreach ($this-&gt;roles-&gt;toArray() AS $role) {
                  $roles[] = $role-&gt;getName();
              }
              return $roles;
          }
  
          /**
           * Returns the password used to authenticate the user.
           *
           * This should be the encoded password. On authentication, a plain-text
           * password will be salted, encoded, and then compared to this value.
           *
           * @return string The password
           */
          public function getPassword(): string
          {
              return $this-&gt;password;
          }
  
          /**
           * @param string $password
           */
          public function setPassword(string $password): void
          {
              $this-&gt;password = $password;
          }
  
          /**
           * Returns the salt that was originally used to encode the password.
           *
           * This can return null if the password was not encoded using a salt.
           *
           * @return string|null The salt
           */
          public function getSalt()
          {
              // See "Do you need to use a Salt?" at https://symfony.com/doc/current/cookbook/security/entity_provider.html
              // we're using bcrypt in security.yml to encode the password, so
              // the salt value is built-in and you don't have to generate one
  
              return null;
          }
  
          /**
           * Returns the username used to authenticate the user.
           *
           * @return string The username
           */
          public function getUsername()
          {
              return $this-&gt;username;
          }
  
          /**
           * @param string $username
           */
          public function setUsername(string $username): void
          {
              $this-&gt;username = $username;
          }
  
          /**
           * Removes sensitive data from the user.
           *
           * This is important if, at any given point, sensitive information like
           * the plain-text password is stored on this object.
           */
          public function eraseCredentials()
          {
              // if you had a plainPassword property, you'd nullify it here
              $this-&gt;plainPassword = null;
          }
      }
  </code></pre>
  
  <p>Role.php file:</p>
  
  <pre><code>    &lt;?php
  
      namespace App\Entity;
  
      use DateTime;
      use Doctrine\Common\Collections\ArrayCollection;
      use Doctrine\ORM\Mapping as ORM;
  
      /**
       * Role
       *
       * @ORM\Table(name="role", uniqueConstraints={@ORM\UniqueConstraint(name="name", columns={"name"})})
       * @ORM\Entity(repositoryClass="App\Repository\RoleRepository")
       */
      class Role
      {
          /**
           * @var ArrayCollection
           *
           * @ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="roles", cascade={"remove"})
           * @ORM\JoinTable(name="users_roles",
           *      joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")},
           *      inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
           *      )
           */
          protected $users;
          /**
           * @var int
           *
           * @ORM\Column(name="id", type="smallint", nullable=false, options={"unsigned"=true})
           * @ORM\Id
           * @ORM\GeneratedValue(strategy="AUTO")
           */
          private $id;
          /**
           * @var string
           *
           * @ORM\Column(name="name", type="string", length=255, nullable=false)
           */
          private $name;
          /**
           * @var DateTime
           *
           * @ORM\Column(name="created_at", type="datetime", nullable=false)
           */
          private $createdAt;
          /**
           * @var DateTime
           *
           * @ORM\Column(name="updated_at", type="datetime", nullable=false)
           */
          private $updatedAt;
  
          /**
           * Role constructor.
           */
          public function __construct()
          {
              $this-&gt;users = new ArrayCollection();
          }
  
          /**
           * @return array
           */
          public function getUsers(): array
          {
              return $this-&gt;users-&gt;toArray();
          }
  
          /**
           * @return int
           */
          public function getId(): int
          {
              return $this-&gt;id;
          }
  
          /**
           * @param int $id
           */
          public function setId(int $id): void
          {
              $this-&gt;id = $id;
          }
  
          /**
           * @return string
           */
          public function getName(): string
          {
              return $this-&gt;name;
          }
  
          /**
           * @param string $name
           */
          public function setName(string $name): void
          {
              $this-&gt;name = $name;
          }
  
          /**
           * @return DateTime
           */
          public function getCreatedAt(): DateTime
          {
              return $this-&gt;createdAt;
          }
  
          /**
           * @param DateTime $createdAt
           */
          public function setCreatedAt(DateTime $createdAt): void
          {
              $this-&gt;createdAt = $createdAt;
          }
  
          /**
           * @return DateTime
           */
          public function getUpdatedAt(): DateTime
          {
              return $this-&gt;updatedAt;
          }
  
          /**
           * @param DateTime $updatedAt
           */
          public function setUpdatedAt(DateTime $updatedAt): void
          {
              $this-&gt;updatedAt = $updatedAt;
          }
      }
  </code></pre>
  
  <p>My data fixtures AppFixtures.php:</p>
  
  <pre><code>    &lt;?php
  
      namespace App\DataFixtures;
  
      use App\Entity\Role;
      use App\Entity\User;
      use DateTime;
      use Doctrine\Bundle\FixturesBundle\Fixture;
      use Doctrine\Common\Persistence\ObjectManager;
      use Doctrine\ORM\EntityManagerInterface;
      use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  
      /**
       * Class AppFixtures
       * @package App\DataFixtures
       */
      class AppFixtures extends Fixture
      {
          /**
           * @var UserPasswordEncoderInterface
           */
          private $encoder;
          /**
           * @var EntityManagerInterface
           */
          private $entityManager;
  
          /**
           * AppFixtures constructor.
           * @param UserPasswordEncoderInterface $userPasswordEncoder
           * @param EntityManagerInterface $entityManager
           */
          public function __construct(UserPasswordEncoderInterface $userPasswordEncoder, EntityManagerInterface $entityManager)
          {
              $this-&gt;encoder = $userPasswordEncoder;
              $this-&gt;entityManager = $entityManager;
          }
  
          /**
           * @param ObjectManager $manager
           */
          public function load(ObjectManager $manager)
          {
              //Creating default roles
              $role = new Role();
              $role-&gt;setName('ROLE_USER');
              $role-&gt;setCreatedAt(new DateTime());
              $role-&gt;setUpdatedAt(new DateTime());
              $manager-&gt;persist($role);
              $role = new Role();
              $role-&gt;setName('ROLE_MODERATOR');
              $role-&gt;setCreatedAt(new DateTime());
              $role-&gt;setUpdatedAt(new DateTime());
              $manager-&gt;persist($role);
              $role = new Role();
              $role-&gt;setName('ROLE_ADMIN');
              $role-&gt;setCreatedAt(new DateTime());
              $role-&gt;setUpdatedAt(new DateTime());
              $manager-&gt;persist($role);
              $manager-&gt;flush();
              $manager-&gt;clear();
              //Creating users
              $user = new User();
              $user-&gt;setUserName('john');
              $user-&gt;setEmail('john@localhost');
              //$user-&gt;setRoles(['ROLE_USER', 'ROLE_JOHN']);
              //$roleAssociation = null;
              $user-&gt;setPassword($this-&gt;encoder-&gt;encodePassword($user, 'test'));
              $user-&gt;setCreatedAt(new DateTime());
              $user-&gt;setUpdatedAt(new DateTime());
              $user-&gt;setIsVerified(true);
              $user-&gt;setIsEnabled(true);
              //$manager-&gt;persist($roleAssociation);
              $manager-&gt;persist($user);
  
              $user = new User();
              $user-&gt;setUserName('tom');
              $user-&gt;setEmail('tom@localhost');
              //$user-&gt;setRoles(['ROLE_USER', 'ROLE_TOM', 'ROLE_MODERATOR']);
              //$roleAssociation = null;
              $user-&gt;setPassword($this-&gt;encoder-&gt;encodePassword($user, 'test'));
              $user-&gt;setCreatedAt(new DateTime());
              $user-&gt;setUpdatedAt(new DateTime());
              $user-&gt;setIsVerified(true);
              $user-&gt;setIsEnabled(true);
              //$manager-&gt;persist($roleAssociation);
              $manager-&gt;persist($user);
  
              $user = new User();
              $user-&gt;setUserName('jimmy');
              $user-&gt;setEmail('jimmy@localhost');
              //$user-&gt;setRoles(['ROLE_USER', 'ROLE_JIMMY', 'ROLE_ADMIN']);
              //$roleAssociation = null;
              $user-&gt;setPassword($this-&gt;encoder-&gt;encodePassword($user, 'test'));
              $user-&gt;setCreatedAt(new DateTime());
              $user-&gt;setUpdatedAt(new DateTime());
              $user-&gt;setIsVerified(true);
              $user-&gt;setIsEnabled(true);
              //$manager-&gt;persist($roleAssociation);
              $manager-&gt;persist($user);
  
              $manager-&gt;flush();
              $manager-&gt;clear();
          }
      }
  </code></pre>
  
  <p>I'm looking for advises for:</p>
  
  <ol>
  <li><p>User entity is cached in annotation, because symfony on each request loads it. config part:</p>
  
  <pre><code>orm:
      metadata_cache_driver:
        type: redis
      result_cache_driver:
        type: redis
      query_cache_driver:
        type: redis
  </code></pre></li>
  </ol>
  
  <p>I'm caching all the data for 1min in redis. Any better solution?</p>
  
  <ol start="2">
  <li><p>DB schema creates Foreign Keys (FK) and extra indexes on users_roles 
  table and I would love to not to have FK, because IMHO it's "heavy" thing. I would prefer to have primary key on (user_id,role_id) only. Any recommendations of this?</p></li>
  <li><p>The solution for having flexible add/remove for user + role + roles_users</p></li>
  </ol>
  
  <p>Big thanks!</p>