Symfony many role for user - php

I have problem, early my entity have one role and this is sufficiently for project. But now my entity need have many role like ROLE_DEVELOPER, ROLE_COMPANY and when user doing some operation for entity add new role. And how best away for this situation, create new entity or change field role in array or what ?
I have entity in table user and have one ROLE_USER and now user want to create Team, for team needed role ROLE_TEAM, and how to set new role? Then User want to create client entity and I need set ROLE_CLIENT. If I create ManyToMany relation how my user, who have many roles authentication? And in user profile I want create, if user have ROLE_TEAM - visible button for go to profile team, buit his many role, I need foreach array roles ?
/**
* Users
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Artel\ProfileBundle\Entity\UsersRepository")
* #ExclusionPolicy("all")
*/
class Users implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #Expose()
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Gedmo\Slug(fields={"firstName", "lastName"}, separator="-", updatable=true)
* #ORM\Column(name="name", type="string", options={"default": "Company"}, length=255, nullable=false)
* #Assert\Length(min=3, max=255)
*/
protected $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=80, nullable=true)
*/
protected $password;
/**
* #ORM\ManyToMany(targetEntity="Role")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*
* #var ArrayCollection $userRoles
*/
protected $userRoles;
/**
* Constructor
*/
public function __construct()
{
$this->userRoles = new ArrayCollection();
}
/**
* Get salt
*
* #return string
*/
public function getSalt()
{
return '';
}
/**
* #inheritDoc
*/
public function eraseCredentials() { }
/**
* Геттер для ролей пользователя.
*
* #return ArrayCollection A Doctrine ArrayCollection
*/
public function getUserRoles()
{
return $this->userRoles;
}
/**
* Геттер для массива ролей.
*
* #return array An array of Role objects
*/
public function getRoles()
{
return $this->getUserRoles()->toArray();
}
}
entity Role
/**
* #ORM\Entity
* #ORM\Table(name="role")
*/
class Role implements RoleInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
* #var integer $id
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*
* #var string $name
*/
protected $name;
/**
* #ORM\Column(type="datetime", name="created_at")
*
* #var DateTime $createdAt
*/
protected $createdAt;
/**
* Геттер для id.
*
* #return integer The id.
*/
public function getId()
{
return $this->id;
}
/**
* Геттер для названия роли.
*
* #return string The name.
*/
public function getName()
{
return $this->name;
}
/**
* Сеттер для названия роли.
*
* #param string $value The name.
*/
public function setName($value)
{
$this->name = $value;
}
/**
* Геттер для даты создания роли.
*
* #return DateTime A DateTime object.
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Конструктор класса
*/
public function __construct()
{
$this->createdAt = new \DateTime();
}
/**
* Реализация метода, требуемого интерфейсом RoleInterface.
*
* #return string The role.
*/
public function getRole()
{
return $this->getName();
}
}
now I crate fixtures but have error:
Artel\ProfileBundle\Entity\Role:
role0:
name: 'ROLE_FREELANCER'
role1:
name: 'ROLE_COMPANY'
role2:
name: 'ROLE_DEVELOPER'
Artel\ProfileBundle\Entity\Users:
users0:
........
userRoles: #role0
users{1..100}:
.......
userRoles: 2x #role*
[Symfony\Component\Debug\Exception\ContextErrorException]
Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections \ArrayCollection::__construct() must be of the type array, object given, called in /var/www/aog-code/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 605 and defined

First, you can use hierarchical roles, so you can add to your config something like:
# app/config/security.yml
security:
# ...
role_hierarchy:
ROLE_DEVELOPER: ROLE_USER
ROLE_COMPANY: [ROLE_ADMIN, ROLE_DEVELOPER]
This means if your user have ROLE_COMPANY, he also have ROLE_ADMIN, ROLE_DEVELOPER and ROLE_USER (because ROLE_DEVELOPER has ROLE_USER)
In case, if you want to have separate roles for each user, you can create Role entity, that should implementing Symfony\Component\Security\Core\Role\RoleInterface.
Then you can just create many-to-many reference between Role and User.
If you have any questions, please, let me know.

it is possible to save many role in table user fields roles this way:
{"roles": ["ROLE_USER", "ROLE_ADMIN"]} (json format) and update getRoles() methode in User entity:
public function getRoles(): array
{
$roles = $this->roles["roles"];
// guarantee every user at least has ROLE_USER
return array_unique($roles);
}

Related

Symfony 4 - load fixtures for dynamic user roles

Currently I'm trying to modify my classes and looking for idea to save dynamic relations between users and roles.
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:
...
$user = new User();
$user->setName($_POST['name']);
$user->setPassword($_POST['password']);
...
$user->setRole('ROLE_USER');//Role for everyone
...
$role = new Role();
$role->setName('ROLE_' . strtoupper($_POST['name']) );//Role for personal use
...
//Here need to implement user+role association (I'm looking for recommendations)
...
$entityManager->persist($user);
$entityManager->persist($role);
//Persist role+user assosiacion
$entityManager->flush();
$entityManager->clear();
My User.php :
<?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->id;
}
/**
* #return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* #param string $email
*/
public function setEmail(string $email): void
{
$this->email = $email;
}
/**
* #return bool
*/
public function isEnabled(): bool
{
return $this->isEnabled;
}
/**
* #param bool $isEnabled
*/
public function setIsEnabled(bool $isEnabled): void
{
$this->isEnabled = $isEnabled;
}
/**
* #return bool
*/
public function isVerified(): bool
{
return $this->isVerified;
}
/**
* #param bool $isVerified
*/
public function setIsVerified(bool $isVerified): void
{
$this->isVerified = $isVerified;
}
/**
* #return DateTime
*/
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
/**
* #param DateTime $createdAt
*/
public function setCreatedAt(DateTime $createdAt): void
{
$this->createdAt = $createdAt;
}
/**
* #return DateTime
*/
public function getUpdatedAt(): DateTime
{
return $this->updatedAt;
}
/**
* #param DateTime $updatedAt
*/
public function setUpdatedAt(DateTime $updatedAt): void
{
$this->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 -> 4.1; https://github.com/symfony/symfony-docs/pull/9914
*/
public function serialize(): string
{
// add $this->salt too if you don't use Bcrypt or Argon2i
return serialize([$this->id, $this->username, $this->password]);
}
/**
* Constructs the object
* #link http://php.net/manual/en/serializable.unserialize.php
* #param string $serialized <p>
* The string representation of the object.
* </p>
* #return void
* #since 5.1.0
*/
public function unserialize($serialized): void
{
// add $this->salt too if you don't use Bcrypt or Argon2i
[$this->id, $this->username, $this->password] = unserialize($serialized, ['allowed_classes' => false]);
}
/**
* Returns the roles granted to the user.
*
* <code>
* public function getRoles()
* {
* return array('ROLE_USER');
* }
* </code>
*
* 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->roles->toArray() AS $role) {
$roles[] = $role->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->password;
}
/**
* #param string $password
*/
public function setPassword(string $password): void
{
$this->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->username;
}
/**
* #param string $username
*/
public function setUsername(string $username): void
{
$this->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->plainPassword = null;
}
}
Role.php file:
<?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->users = new ArrayCollection();
}
/**
* #return array
*/
public function getUsers(): array
{
return $this->users->toArray();
}
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* #return DateTime
*/
public function getCreatedAt(): DateTime
{
return $this->createdAt;
}
/**
* #param DateTime $createdAt
*/
public function setCreatedAt(DateTime $createdAt): void
{
$this->createdAt = $createdAt;
}
/**
* #return DateTime
*/
public function getUpdatedAt(): DateTime
{
return $this->updatedAt;
}
/**
* #param DateTime $updatedAt
*/
public function setUpdatedAt(DateTime $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
}
My data fixtures AppFixtures.php:
<?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->encoder = $userPasswordEncoder;
$this->entityManager = $entityManager;
}
/**
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager)
{
//Creating default roles
$role = new Role();
$role->setName('ROLE_USER');
$role->setCreatedAt(new DateTime());
$role->setUpdatedAt(new DateTime());
$manager->persist($role);
$role = new Role();
$role->setName('ROLE_MODERATOR');
$role->setCreatedAt(new DateTime());
$role->setUpdatedAt(new DateTime());
$manager->persist($role);
$role = new Role();
$role->setName('ROLE_ADMIN');
$role->setCreatedAt(new DateTime());
$role->setUpdatedAt(new DateTime());
$manager->persist($role);
$manager->flush();
$manager->clear();
//Creating users
$user = new User();
$user->setUserName('john');
$user->setEmail('john#localhost');
//$user->setRoles(['ROLE_USER', 'ROLE_JOHN']);
//$roleAssociation = null;
$user->setPassword($this->encoder->encodePassword($user, 'test'));
$user->setCreatedAt(new DateTime());
$user->setUpdatedAt(new DateTime());
$user->setIsVerified(true);
$user->setIsEnabled(true);
//$manager->persist($roleAssociation);
$manager->persist($user);
$user = new User();
$user->setUserName('tom');
$user->setEmail('tom#localhost');
//$user->setRoles(['ROLE_USER', 'ROLE_TOM', 'ROLE_MODERATOR']);
//$roleAssociation = null;
$user->setPassword($this->encoder->encodePassword($user, 'test'));
$user->setCreatedAt(new DateTime());
$user->setUpdatedAt(new DateTime());
$user->setIsVerified(true);
$user->setIsEnabled(true);
//$manager->persist($roleAssociation);
$manager->persist($user);
$user = new User();
$user->setUserName('jimmy');
$user->setEmail('jimmy#localhost');
//$user->setRoles(['ROLE_USER', 'ROLE_JIMMY', 'ROLE_ADMIN']);
//$roleAssociation = null;
$user->setPassword($this->encoder->encodePassword($user, 'test'));
$user->setCreatedAt(new DateTime());
$user->setUpdatedAt(new DateTime());
$user->setIsVerified(true);
$user->setIsEnabled(true);
//$manager->persist($roleAssociation);
$manager->persist($user);
$manager->flush();
$manager->clear();
}
}
I'm looking for advises for:
User entity is cached in annotation, because symfony on each request loads it. config part:
orm:
metadata_cache_driver:
type: redis
result_cache_driver:
type: redis
query_cache_driver:
type: redis
I'm caching all the data for 1min in redis. Any better solution?
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?
The solution for having flexible add/remove for user + role + roles_users
Big thanks!
I'll try to answer this but as I mentioned in my comment, this question is a bit large so some of this may be generalized.
User entity is cached in annotation, because symfony on each request
loads it. config part:
orm:
metadata_cache_driver:
type: redis
result_cache_driver:
type: redis
query_cache_driver:
type: redis
I'm caching all the data for 1min in redis. Any better solution?
When you say "better" here, it's subjective. Each application is different. Caches, and the length at which caches stay alive, is specific to each application's requirements. I don't know if there's anything inherently wrong with this, but it's largely dependent on your app's requirements.
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?
First, FKs are important, and they're intended to keep integrity in your data. They ensure that if someone attempted to delete a user or role that a user_roles row linked to, the operation would fail. You usually want this behavior so that you don't lose data or create orphaned data.
Secondly, I'm not sure what version of Doctrine you're using but my similar ManyToMany tables do create a PK and unique index on (user_id, role_id).
The solution for having flexible add/remove for user + role + roles_users
You do this by using Doctrine's ArrayCollections (click Collections in the menu to make sure the anchor link worked, they're broken sometimes).
There is one caveat when doing this with the the default Symfony User entity though. It implements the Symfony\Component\Security\Core\User\UserInterface interface which defines a getRoles() method that is meant to return an array of roles as strings. This is so that certain Symfony security features work as expected. This means that if you have a private $roles property that you will have to rename its standard Doctrine getter to something else so that you can leave getRoles() functioning as expected.
So, for the User entity, I typically just rename my getter, setter, adder, and remover to something like getUserRoles(), setUserRoles(), addUserRole(), and removeUserRole() and then I leave getRoles() to implement the expected interface.
Here is an incomplete example of a user class with no Role class example.
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection/*Interface*/;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use \InvalidArgumentException;
use function array_unique;
class User implements UserInterface
{
/* ... */
/**
* #var Role[]|array User's roles
*
* Note that the getter, setter, adder, and remover for this method are renamed so that the getRoles() method that
* we implement from Symfony\Component\Security\Core\User\UserInterface can function as expected.
*
* #see UserInterface
* #see User::getRoles()
* #see User::getUserRoles()
* #see User::setUserRoles()
* #see User::addUserRole()
* #see User::removeUserRole()
*
* #ORM\ManyToMany(targetEntity="App\Entity\Role", inversedBy="users", cascade={"persist"})
*/
private $roles;
/* ... */
/**
* Constructor
*/
public function __construct()
{
$this->roles = new ArrayCollection();
}
/* ... */
/**
* #return Collection|Role[]
*/
public function getUserRoles(): Collection
{
return $this->roles;
}
/**
* #param Collection|Role[] $roles
*
* #return self
*/
public function setUserRoles(Collection/*Interface*/ $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #param Role $role
*
* #return self
*/
public function addUserRole(Role $role): self
{
$this->roles->add($role);
return $this;
}
/**
* #param Role $role
*
* #return self
*/
public function removeUserRole(Role $role): self
{
$this->roles->removeElement($role);
return $this;
}
/**
* Get array of roles as strings
*
* This method is an implementation of UserInterface::getRoles(). The getter for self::$roles is
* self::getUserRoles().
*
* #return string[]|array
*
* #see UserInterface
*/
public function getRoles()
{
$roleStrings = [];
foreach ($this->roles as $role) {
$roleStrings[] = $role->getName();
}
// guarantee every user at least has ROLE_USER
$roleStrings[] = 'ROLE_USER';
return array_unique($roleStrings);
}
/* ... */
}
You can also do the reverse for the Role object if you wanted to, but this depends on if you wanted to add users to roles that way and it's often best to choose the owning side of the relation.
Here is an example of how this can be used anywhere where you're working with entities, fixtures or otherwise.
<?php
use Doctrine\Common\Collections\ArrayCollection;
use App\Entity\User;
use App\Entity\Role;
$entityManager = (get entity manager);
$user = new User();
// You can also add the name as an argument to Role::__construct()
// then call from setName() from that if you wanted to simplify this.
$roleFoo = (new Role())->setName('ROLE_FOO');
$roleBar = (new Role())->setName('ROLE_BAR');
// set roles using ArrayCollection of roles.
$user->setUserRoles(new ArrayCollection($roleFoo, $roleBar));
// add new role, see User::addUserRole() for
$user->addUserRole((new Role()->setName('ROLE_WIN'));
// remove ROLE_BAR
// You can also do this with entities that you find with Doctrine
// if you want to remove them from a persisted collection.
$user->removeUserRole($roleBar);
// get roles as a collection of Role objects.
// This will return either ArrayCollection or PersistentCollection
// depending on context. These are objects that act like arrays
// and each element will be a Role object.
$roles = $user->getUserRoles();
// get roles as strings... mostly used by Symfony's security system
// but could be used by your app too.
$roleStrings = $user->getRoles();

Embedded forms relations doctrine

in my symfony app, i'm using embedded forms. In my case, an object "CompetenceGroupe" can have multiple objects "CompetenceItem", but an object "CompetenceItem" belongs to only one object "CompetenceGroupe", so the relation is manyToOne.
The form work perfectly, and I have two tables (one for each entity), and it's well saved in the database.
But when I select an CompetenceGroupe object with doctrine in my controller, I have all informations of the object, and he's got an empty "competenceItems" property, so I can't retrieve the childs object (CompetenceItem).
My "CompetenceGroupe" entity :
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="competences_groupes")
*/
class CompetenceGroupe
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id_competence_groupe;
/**
* #var User $user
*
* #ORM\ManyToOne(targetEntity="User", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_user", referencedColumnName="id_user", nullable=false)
*/
private $user;
/**
* #ORM\Column(type="string", length=60, nullable=true)
*/
protected $titre;
protected $competence_items;
public function __construct()
{
$this->competence_items = new ArrayCollection();
}
public function getCompetenceItems()
{
return $this->competence_items;
}
/**
* Get idCompetenceGroupe
*
* #return integer
*/
public function getIdCompetenceGroupe()
{
return $this->id_competence_groupe;
}
/**
* Set titre
*
* #param string $titre
*
* #return CompetenceGroupe
*/
public function setTitre($titre)
{
$this->titre = $titre;
return $this;
}
/**
* Get titre
*
* #return string
*/
public function getTitre()
{
return $this->titre;
}
/**
* Set user
*
* #param \AppBundle\Entity\User $user
*
* #return CompetenceGroupe
*/
public function setUser(\AppBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AppBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
public function addItem(CompetenceItem $item)
{
$this->competence_items->add($item);
}
public function removeItem(CompetenceItem $item)
{
// ...
}
/**
* Set competenceItems
*
* #param \AppBundle\Entity\CompetenceItem $competenceItems
*
* #return CompetenceGroupe
*/
public function setCompetenceItems(\AppBundle\Entity\CompetenceItem $competenceItems = null)
{
$this->competence_items = $competenceItems;
return $this;
}
}
And my "CompetenceItem" entity :
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="competences_items")
*/
class CompetenceItem
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id_competence_item;
/**
* #ORM\Column(type="string", length=60, nullable=false)
*/
protected $libelle;
/**
* #var CompetenceNiveau $niveau
*
* #ORM\ManyToOne(targetEntity="CompetenceNiveau", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_competence_niveau", referencedColumnName="id_competence_niveau", nullable=true)
*/
private $niveau;
/**
* #var CompetenceGroupe $competence_groupe
*
* #ORM\ManyToOne(targetEntity="CompetenceGroupe", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_competence_groupe", referencedColumnName="id_competence_groupe", nullable=false)
*/
private $competence_groupe;
/**
* Get idCompetenceItem
*
* #return integer
*/
public function getIdCompetenceItem()
{
return $this->id_competence_item;
}
/**
* Set libelle
*
* #param string $libelle
*
* #return CompetenceItem
*/
public function setLibelle($libelle)
{
$this->libelle = $libelle;
return $this;
}
/**
* Get libelle
*
* #return string
*/
public function getLibelle()
{
return $this->libelle;
}
/**
* Set niveau
*
* #param \AppBundle\Entity\CompetenceNiveau $niveau
*
* #return CompetenceItem
*/
public function setNiveau(\AppBundle\Entity\CompetenceNiveau $niveau = null)
{
$this->niveau = $niveau;
return $this;
}
/**
* Get niveau
*
* #return \AppBundle\Entity\CompetenceNiveau
*/
public function getNiveau()
{
return $this->niveau;
}
/**
* Set competenceGroupe
*
* #param \AppBundle\Entity\CompetenceGroupe $competenceGroupe
*
* #return CompetenceItem
*/
public function setCompetenceGroupe(\AppBundle\Entity\CompetenceGroupe $competenceGroupe)
{
$this->competence_groupe = $competenceGroupe;
return $this;
}
/**
* Get competenceGroupe
*
* #return \AppBundle\Entity\CompetenceGroupe
*/
public function getCompetenceGroupe()
{
return $this->competence_groupe;
}
}
I think I have a missing annotation of the "competence_items" property in the CompetenceGroupe entity, but i'm really not sure ...
Thanks for your help !
A good practice may be to have a competence form, which would be call inside your competence group form
You may add a CollectionType as parrent and include query to search which competence already exist
There are some good example with post form type in symfony demo blog
Or you can use form events (onSubmit, preSubmit, etc...) to charge your entity with your required competence. This example show a message form which allow to choose friend from preset data, this is a good example.
You have tow choice , even to create a Many-To-One, Unidirectional , in this case , you need clean some code , take a look:
In CompetenceGroupe class :
class CompetenceGroupe
{
/**
* Many competence have One Group.
* #ManyToOne(targetEntity="CompetenceItem")
* #JoinColumn(name="id_competence_item", referencedColumnName="id_competence_item")
*/
protected $competence_items;
public function __construct()
{
// $this->competence_items = new ArrayCollection();
//delete that line
}
In CompetenceItem class :
class CompetenceItem
{
You need to delete private $competence_groupe; attribute with his annotation :
By this way, when you dump a CompetenceGroupe object you gonna find the competence items.
Also, you can do it with One-To-Many, Bidirectional ,if you want to get the data from the inverse side and from the owning side .
EDIT: If one competenceGroupe can have many competenceItems, then that is a OneToMany relationship; this is the inverse side of the relationship as defined by doctrine, but that is ok. Your question asked how to pull a competenceGroupe and retrieve all related competenceItems. You can do this by making the competenceItems an ArrayCollection in your CompetenceGroupe entity, just as you have done. You do have to define that further in the annotation, see (updated) code below.
For an ArrayCollection, you can remove your method setCompetenceItems and instead define a method addCompetenceItem in your CompetenceGroupe entity.
class CompetenceGroupe
{
/**
* #ORM\OneToMany(targetEntity="CompetenceItem", mappedBy="competence_groupe")
*/
protected $competenceItems;
public function __construct()
{
$this->competenceItems= new ArrayCollection();
}
/**
* Add competenceItem
*
* #param CompetenceItem $competenceItem
* #return CompetenceGroupe
*/
public function addCompetenceItem(CompetenceItem $competenceItem)
{
$this->competence_items->add($competenceItem);
return $this;
}
}
You'll also need to define the owning side to make all this work.

How to map a double Many To One relationship to the same table in Doctrine 2?

I have two tables created.
Players Message
________ _________
id ----------- id
|
username -- sender_id
|
password -- receiver_id
sent_on
text
The Entities I have so far are:
AppBundle\Entity\Message
class Message
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="text", type="text")
*/
private $text;
/**
* #var \DateTime
*
* #ORM\Column(name="sent_on", type="datetime")
*/
private $sentOn;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set text
*
* #param string $text
*
* #return Message
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get text
*
* #return string
*/
public function getText()
{
return $this->text;
}
/**
* Set sentOn
*
* #param \DateTime $sentOn
*
* #return Message
*/
public function setSentOn($sentOn)
{
$this->sentOn = $sentOn;
return $this;
}
/**
* Get sentOn
*
* #return \DateTime
*/
public function getSentOn()
{
return $this->sentOn;
}
AppBundle\Entity\Player
class Player implements UserInterface
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=100, unique=true)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, unique=true)
*/
private $email;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255)
*/
private $password;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* #param string $username
*
* #return Player
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Get username
*
* #return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set password
*
* #param string $password
*
* #return Player
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Returns the roles granted to the user.
*
* <code>
* public function getRoles()
* {
* return array('ROLE_USER');
* }
* </code>
*
* 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 (Role|string)[] The user roles
*/
public function getRoles()
{
return ['ROLE_USER'];
}
/**
* 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()
{
return null;
}
/**
* 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()
{
}
}
How should I connect these two tables? Is the connection ManyToMany or just ManyToOne <-> OneToMany? I want to achieve the same as
SELECT *
FROM messages, players
WHERE (players.id = messages.sender_id OR players.id = messages.receiver_id) AND players.id = 1
ManyToMany or ManyToOne?
It's clearly a ManyToOne scenario, in a ManyToMany scenario you would have a third table, an intermediate table.
How connect theses two tables
class Message
{
//columns here
//...
/**
* #ORM\ManyToOne(target="Player", inversedBy="receivedMessages")
* #ORM\JoinColumn(name="receiver_id",referencedColumnName="id")
*/
protected $receiver;
/**
* #ORM\ManyToOne(target="Player", inversedBy="sentMessages")
* #ORM\JoinColumn(name="sender_id",referencedColumnName="id")
*/
protected $sender;
// create getters & setters
}
class Player implements UserInterface
{
//columns here
//...
/**
* #ORM\OneToMany(target="Message", mappedBy="receiver")
*/
protected $receivedMessages;
/**
* #ORM\OneToMany(target="Message", mappedBy="sender")
*/
protected $sentMessages;
// create getters & setters
}
SQL to DQL
SQL:
SELECT *
FROM messages, players
WHERE (players.id = messages.sender_id OR players.id = messages.receiver_id)
AND players.id = 1
Notice, in DQL you need to think in OOP (which is usually a much more natural thought):
SELECT m, s, r
FROM \AppBundle\Entity\Message m
INNER JOIN m.sender s
INNER JOIN m.receiver r
WHERE s.id = 1 OR r.id = 1

Port a MySQL query to Doctrine

I'm trying to setup a one-to-many database, using Doctrine. One User, can have multiple Roles, and when I get the User, I want to get the associated Roles. I have a raw MySQL query that does that.
SELECT
u.*,
GROUP_CONCAT(r.role) as roles
FROM
users u
INNER JOIN user_roles ur ON ur.user_id = u.id
INNER JOIN roles r ON r.id = ur.role_id
GROUP BY id;
+----+----------+------------------------+--------------------------------------------------------------+-----------+----------------------+
| id | username | email | password | is_active | GROUP_CONCAT(r.role) |
+----+----------+------------------------+--------------------------------------------------------------+-----------+----------------------+
| 1 | admin | my#email.address | $2a$08$jHZj/wJfcVKlIwr5AvR78euJxYK7Ku5kURNhNx.7.CSIJ3Pq6LEPC | 1 | ROLE_USER,ROLE_ADMIN |
+----+----------+------------------------+--------------------------------------------------------------+-----------+----------------------+
Is this something I can actually automatically replicate in Doctrine? And if so, where abouts am I going wrong? Here are my current (broken) classes (apologies for the code dump).
The error this is currently logging is:
[2016-12-20 12:01:35] security.INFO: Authentication request failed. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\Auth
enticationServiceException(code: 0): Notice: Undefined index: user at /var/www/vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authe
ntication/Provider/DaoAuthenticationProvider.php:94, Symfony\\Component\\Debug\\Exception\\ContextErrorException(code: 0): Notice: Undefined in
dex: user at /var/www/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:1768)"} []
User.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
...
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="UserRoles", mappedBy="user", fetch="EAGER")
*/
private $roles;
public function __construct()
{
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid(null, true));
$this->roles = new ArrayCollection();
}
...
public function getRoles()
{
return $this->roles;
}
/**
* Add role
*
* #param \AppBundle\Entity\UserRoles $role
*
* #return User
*/
public function addRole(\AppBundle\Entity\UserRoles $role)
{
$this->roles[] = $role;
return $this;
}
/**
* Remove role
*
* #param \AppBundle\Entity\UserRoles $role
*/
public function removeRole(\AppBundle\Entity\UserRoles $role)
{
$this->roles->removeElement($role);
}
}
Role.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Role
*
* #ORM\Table(name="roles")
* #ORM\Entity(repositoryClass="AppBundle\Repository\RoleRepository")
*/
class Role
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="role", type="string", length=255, unique=true)
*/
private $role;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set role
*
* #param string $role
*
* #return Role
*/
public function setRole($role)
{
$this->role = $role;
return $this;
}
/**
* Get role
*
* #return string
*/
public function getRole()
{
return $this->role;
}
}
UserRoles.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* UserRoles
*
* #ORM\Table(name="user_roles")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRolesRepository")
*/
class UserRoles
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="userroles")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $userId;
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Role")
* #ORM\JoinColumn(name="role_id", referencedColumnName="id")
*/
private $roleId;
/**
* Set userId
*
* #param integer $userId
*
* #return UserRoles
*/
public function setUserId($userId)
{
$this->userId = $userId;
return $this;
}
/**
* Get userId
*
* #return int
*/
public function getUserId()
{
return $this->userId;
}
/**
* Set roleId
*
* #param integer $roleId
*
* #return UserRoles
*/
public function setRoleId($roleId)
{
$this->roleId = $roleId;
return $this;
}
/**
* Get roleId
*
* #return int
*/
public function getRoleId()
{
return $this->roleId;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
In the $roles association inside your User entity you set the mappedBy attribute to user, but your UserRoles entity does not have such a field. Instead it has a field called userId.
Doctrine tries to find the association using your mapping. The user value from your #mappedBy attribute cannot be found in the database, hence the error message:
Undefined index...
Since you are mapping objects you should reconsider the naming convention inside your UserRoles entity. So don't use $userId and $roleId but simply use User and Role and also set dependency injection and return values inside your setters and getters to match User and Role classes.
Since UserRoles is an entity (and not a collection) I would also suggest renaming it to UserRole (singular). It will help you understand what you have.
Start with changing your UserRoles like this:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\User;
use AppBundle\Entity\Role;
/**
* UserRole
*
* #ORM\Table(name="user_roles")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRoleRepository")
*/
class UserRole
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var User
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="userRoles")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #var Role
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Role")
* #ORM\JoinColumn(name="role_id", referencedColumnName="id")
*/
private $role;
/**
* Set user
*
* #param User $user
* #return UserRole
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return User
*/
public function getUser()
{
return $this->user;
}
/**
* Set role
*
* #param Role $role
* #return UserRole
*/
public function setRole(Role $role)
{
$this->role = $role;
return $this;
}
/**
* Get role
*
* #return Role
*/
public function getRole()
{
return $this->role;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
And change your User entity to match these changes accordingly:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\UserRole;
use Serializable;
/**
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements UserInterface, Serializable
{
...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="UserRole", mappedBy="user", fetch="EAGER")
*/
private $userRoles;
public function __construct()
{
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid(null, true));
$this->userRoles = new ArrayCollection();
}
...
/**
* #return Collection
*/
public function getUserRoles()
{
return $this->userRoles;
}
/**
* Add user role
*
* #param UserRole $userRole
*
* #return User
*/
public function addUserRole(UserRole $userRole)
{
$this->userRoles[] = $userRole;
return $this;
}
/**
* Remove role
*
* #param UserRole $userRole
*/
public function removeUserRole(UserRole $userRole)
{
$this->userRoles->removeElement($userRole);
}
}
Since your the repository for your User class is called UserRepository I also renamed UserRolesRepository to UserRoleRepository (singular).

In Doctrine, do I need do create a class only to make a join?

I'm trying to make a join in my repository class (Symfony 3 with Doctrine).
This is how it looks like:
public function findByRole($roles){
$qb = $this->createQueryBuilder('u')
->join('user_role', 'ur', Join::ON, 'ur.id = u.customerId');
$q = $qb->getQuery();
$users = $q->getArrayResult();
dump($users);
}
And I've got this error:
[Semantical Error] line 0, col 49 near 'user_role ur': Error: Class
'user_role' is not defined.
There are two entity classes defined - User and Role. And Role is a property of User (ManyToMany).
There is also a joining table - user_role - do I need to create a class for every joining table even if I don't need it for my own purposes?
p.s.
I know this error can be already found on Stackoverflow, but people having this issue probably have different problems.
Update:
Here's the User entity class (shortened):
/**
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//irrelevant code removed
/**
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Role", cascade = {"persist"}, inversedBy="users")
* #ORM\JoinTable(name="user_role",
* joinColumns={#ORM\JoinColumn(name="user_id",
* referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id",
* referencedColumnName="id")}
* )
*/
private $roles;
//////////////////////////////////////////////////////////////////////
public function __construct() {
$this->roles = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
////////////////////////////////
//roles
/**
* Add role
* #param \AppBundle\Entity\Role $role
*
* #return User
*/
public function addRole(\AppBundle\Entity\Role $role) {
$this->roles[] = $role;
return $this;
}
public function setRoles(\AppBundle\Entity\Role $role){
$this->addRole($role);
return $this;
}
/**
* Remove role
* #param \AppBundle\Entity\Role $role
*/
public function removeRole(\AppBundle\Entity\Role $role) {
$this->roles->removeElement($role);
}
/**
* I know it probably should return simply $this->roles, but the interface I'm implementing requires an array of strings... But everything works fine, except joining user+roles
* #return \Doctrine\Common\Collections\Collection
*/
public function getRoles() {
return $this->roles->toArray();
}
/**
* Get roles
* #return \Doctrine\Common\Collections\Collection
*/
public function getRoleEntities() {
return $this->roles;
}
}
I doctrine, all joined objects must be entities.
But you can map a many-to-many relationship between the entities User and Role, using user_role as a linkage table
In user table you might have a property $roles
/**
* #ManyToMany(targetEntity="Role", inversedBy="users")
* #JoinTable(name="user_role",
* joinColumns={#JoinColumn(name="user_id",
* referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="role_id",
* referencedColumnName="id")}
* )
*/
private $roles;
and a property $users in role table
/**
* #ManyToMany(targetEntity="User", inversedBy="roles")
* #JoinTable(name="user_role",
* joinColumns={#JoinColumn(name="role_id",
* referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="user_id",
* referencedColumnName="id")}
* )
*/
private $users;
I've not tested this code. Surely It gonna need some tweeking.
You also need to created the getters and setters for these properties.

Categories