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();
Related
I would like to know how to pass UserInterface as a parameter to the encodePassword function. Actually, when running the following command line php bin/console doctrine:fixtures:load , I got this message error:
Argument 1 passed to Symfony\Component\Security\Core\Encoder\UserPasswordEncoder::encodePassword() must be an instance of Symfony\Component\Security\Core\User\UserInterface, instance of App\Entity\User given,
These are my relevant files:
User.php :
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
* #UniqueEntity(fields="email", message="Un utilisateur existe déjà avec cet email.")
*/
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="string", length=255)
*/
private $prenom;
/**
* #ORM\Column(type="text")
*/
private $roles;
/**
* #ORM\Column(type="string", length=255)
*/
private $email;
/**
* #ORM\Column(type="string", length=255)
*/
private $password;
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getRoles(): ?string
{
return $this->roles;
}
public function setRoles(string $roles): self
{
$this->roles = $roles;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
// public function getRoles(){
// return [
// 'ROLE_ADMIN'
// ];
// }
public function getSalt() {}
public function eraseCredentials() {}
// Some added code
public function serialize(){
return $this->serialize([
$this->id,
$this->email,
$this->password
]);
}
public function unserialize($string){
list(
$this->id,
$this->email,
$this->password
) = unserialize($string, ['allowed_classes' => false]);
}
}
UserFixture.php :
<?php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserFixture extends Fixture
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function load(ObjectManager $manager): void
{
$user = new User();
$user->getNom('test');
$user->setPrenom('test');
$user->setRoles('["ROLE_ADMIN"]');
$user->setEmail('test#gmail.com');
$user->setPassword(
$this->encoder->encodePassword($user,'test')
);
$manager->persist($user);
$manager->flush();
}
}
UserInterface.php :
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien#symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\User;
use Symfony\Component\Security\Core\Role\Role;
/**
* Represents the interface that all user classes must implement.
*
* This interface is useful because the authentication layer can deal with
* the object through its lifecycle, using the object to get the encoded
* password (for checking against a submitted password), assigning roles
* and so on.
*
* Regardless of how your users are loaded or where they come from (a database,
* configuration, web service, etc.), you will have a class that implements
* this interface. Objects that implement this interface are created and
* loaded by different objects that implement UserProviderInterface.
*
* #see UserProviderInterface
*
* #author Fabien Potencier <fabien#symfony.com>
*/
interface UserInterface
{
/**
* Returns the roles granted to the user.
*
* public function getRoles()
* {
* return ['ROLE_USER'];
* }
*
* Alternatively, the roles might be stored in a ``roles`` property,
* and populated in any number of different ways when the user object
* is created.
*
* #return array<Role|string> The user roles
*/
public function getRoles();
/**
* 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|null The encoded password if any
*/
public function getPassword();
/**
* 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();
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername();
/**
* 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();
}
UserPasswordEncoderInterface.php :
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien#symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\Encoder;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* UserPasswordEncoderInterface is the interface for the password encoder service.
*
* #author Ariel Ferrandini <arielferrandini#gmail.com>
*
* #method bool needsRehash(UserInterface $user)
*/
interface UserPasswordEncoderInterface
{
/**
* Encodes the plain password.
*
* #param string $plainPassword The password to encode
*
* #return string The encoded password
*/
public function encodePassword(UserInterface $user, $plainPassword);
/**
* #param string $raw A raw password
*
* #return bool true if the password is valid, false otherwise
*/
public function isPasswordValid(UserInterface $user, $raw);
}
So, what's wrong in my code and how can I fix it. Any idea?
In your user.php file update it to this:
class User implements UserInterface
{...
I use symfony 3.1. I want to create a notification system in my application in symfony. I have created an entity named notification to save notifications. So when a user creates, edits or removes a record in the database, I want to save this action in a notification table. I used HasLifecycleCallbacks() annotation method and it forced me to create a controller object in my entity but nothing has worked. How can i do it? Is there another solution?
/**
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="CM\UserBundle\Repository\UserRepository")
* #ORM\HasLifecycleCallbacks()
*/
class User extends BaseUser {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", unique=true, length=255, nullable=true)
* #Assert\NotBlank()
*/
protected $nom;
/**
* #var int
*
* #ORM\Column(name="numero", type="integer", unique=true, nullable=true)
* #Assert\NotBlank()
*/
protected $numero;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set nom
*
* #param string $nom
*
* #return User
*/
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
/**
* Get nom
*
* #return string
*/
public function getNom()
{
return $this->nom;
}
/**
* Set numero
*
* #param integer $numero
*
* #return User
*/
public function setNumero($numero)
{
$this->numero = $numero;
return $this;
}
/**
* Get numero
*
* #return int
*/
public function getNumero()
{
return $this->numero;
}
/**
* #ORM\PreRemove
*/
public function notify(){
$controlleur = new RemoveController();
$em = $controlleur->getDoctrine()->getManager();
$notif = new Notification();
$notif->setOperation('recording');
$notif->setUser('William');
$em->persist($notif);
$em->flush();
}
}
I've resolved my problem. I had not read very well the documentation. And then i did some more research. So here is a solution that works for me in symfony 3.1.
I used dependency injection. I have injected ManagerRegistry to have the entity manager service and TokenStorage for tokenStorage service to know the current user connected.
I have created a folder name NotificationDB. And then i have also created a class name NotificationDB.php which here is the code. For this example, i suppose that i have a form to register a foo entity.
namespace CM\GestionBundle\NotificationBD;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use \CM\GestionBundle\Entity\Notification;
class NotificationBD {
private $managerRegistry;
/**
* #var TokenStorage
*/
private $tokenStorage;
public function __construct(ManagerRegistry $managerRegistry, TokenStorage $tokenStorage)
{
$this->managerRegistry = $managerRegistry;
$this->tokenStorage = $tokenStorage;
}
public function notifyEvent(){
$entityManager = $this->managerRegistry->getManager();
$user = $this->tokenStorage->getToken()->getUser();
$notif = new Notification();
$notif->setDateNotif(new \Datetime());
$notif->setUser($user);
$notif->setActionNotif('recording');
$notif->setObjetNotif('foo');
$entityManager->persist($notifEdition);
$entityManager->flush();
}
}
In CM\GestionBundle\Resources\config**, i have configure as service in **services.yml file which here is the content :
CM_gestion.notification:
class: CM\GestionBundle\NotificationBD\NotificationBD
arguments: ['#doctrine', '#security.token_storage']
And then i have created a listener for which will call this service. For this example, i will create a foo entity listener which will listen register event and use the service to persist the notification entity. Here is my foo listener.
namespace CM\GestionBundle\DoctrineListener;
use CM\GestionBundle\NotificationBD\NotificationBD;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use \CM\GestionBundle\Entity\Foo;
class FooListener {
/**
* #var NotificationBD
*/
private $notificationBD;
public function __construct(NotificationBD $notificationBD) {
$this->notificationBD = $notificationBD;
}
public function postPersist(LifecycleEventArgs $args) {
$entity = $args->getObject();
if (!$entity instanceof Foo) {
return;
}
$this->notificationBD->notifyEvent();
}
}
In CM\GestionBundle\Resources\config
The last thing to do is to add this listener in services.yml file. Here is the content
CM_gestion.doctrine_listener.foo:
class: CM\GestionBundle\DoctrineListener\FooListener
arguments:
- "#CM_gestion.notification"
tags:
- {name: doctrine.event_listener, event: postPersist}
That all. this solution work for me in symfony 3.1. This problem can be marked as resolved. Thank
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);
}
Simple process,
Need to create Users with mapped Roles.
I followed the step from link
http://symfony.com/doc/current/cookbook/security/entity_provider.html
User and Roles table generated but users_roles table is not generated in MySql...
Will i need to create it manually?
Second
I have configured with User table for Authentication
After login it redirects to Error page,
FatalErrorException: Error: Call to a member function toArray() on a non-object in /var/www/vibilling_3/src/ViBillingPortal/AuthenticationBundle/Entity/users.php line 130
I searched, but i cant find any solutions... Below my code
Bill/PortalBundle/Entity/users.php
namespace ViBillingPortal\AuthenticationBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* users
*/
class users implements UserInterface, \Serializable
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $username;
/**
* #var string
*/
private $password;
/**
* #var string
*/
private $created_date;
/**
* #ORM\ManyToMany(targetEntity="roles", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $userroles;
public function __construct()
{
$this->userroles = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* #param string $username
* #return users
*/
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 users
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set created_date
*
* #param string $created_date
* #return users
*/
public function setCreated_date($password)
{
$this->password = $created_date;
return $this;
}
/**
* Get Created_date
*
* #return string
*/
public function getCreated_date()
{
return $this->created_date;
}
/**
* Get Roles
*/
public function getRoles()
{
return $this->userroles->toArray();
}
/**
* #inheritDoc
*/
public function getSalt()
{
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
}
}
Bill/PortalBundle/Entity/roles.php
namespace ViBillingPortal\AuthenticationBundle\Entity;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* roles
*/
class roles implements RoleInterface, \Serializable
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $name;
/**
* #ORM\Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* #ORM\ManyToMany(targetEntity="users", mappedBy="userroles")
*/
protected $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return roles
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #see RoleInterface
*/
public function getRole()
{
return $this->role;
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
}
}
You should use a ManyToMany relation, not a ManyToOne if you want to use a join table : http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#many-to-many-bidirectional
For the second error, it's strange as you initialize usersroles as an ArrayCollection in your construct method, it should work.
Could you add a var_dump to look what is stored in this property ?
Why don't you get any setter/getter for usersroles ?
I think you should also read Symfony coding standards : http://symfony.com/doc/current/contributing/code/standards.html. You coding style is not consistent.
this methodi use it to add a new job but when i add a job the password of the that current user get its password set to empty cus the user object that i retrieve has no password and
symfony behaves like so for to secure the password any help would be much appreciated
` public function addJobAction(){
if(false === $this->get('security.context')
->isGranted('ROLE_ANNOUNCER')
)
{
throw new AccessDeniedException();
}
$job = new Job() ;
$jobForm = $this->createForm( new JobType() ,$job) ;
$request = $this->getRequest();
if( $request->getMethod() == 'GET'){
return
$this->render('MyJobBundle:Job:addJob.html.twig' ,
array('form'=> $jobForm->createView() )
) ;
}
if( $request->getMethod() == 'POST'){
$jobForm->bindRequest($request);
if( $jobForm->isValid() ){
$user = $this->get('security.context')->getToken()
->getUser();
$job->setAnnouncer($user);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($job) ;
$em->flush() ;
return
$this->redirect($this->generateUrl('show_job' ,
array('id'=> $job->getId() ) )
);
}else{
return
new Response('no');
}
}
}
heres my job entity
namespace My\JobBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use My\UserBundle\Entity\User ;
use Symfony\Component\Validator\Constraints as Assert;
/**
* My\JobBundle\Entity\Job
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\JobBundle\Entity\JobRepository")
*/
class Job
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $content
*
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #var string $city
*
* #ORM\Column(name="city", type="string", length=255)
*
*/
private $city;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var string $salary
*
* #ORM\Column(name="salary", type="string", length=255)
*
*
*/
private $salary;
/**
* #ORM\ManyToOne(targetEntity="My\UserBundle\Entity\User")
*/
private $announcer ;
/**
* link a job to a user
*/
public function setAnnouncer(User $a)
{
$this->announcer = $a;
}
/**
* return a user from a job object
*/
public function getAnnouncer()
{
return $this->announcer;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set content
*
* #param string $content
*/
public function setContent($content)
{
$this->content = $content;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set created_at
*
* #param datetime $createdAt
*/
public function setCreatedAt($createdAt)
{
$this->created_at = $createdAt;
}
/**
* Get created_at
*
* #return datetime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set salary
*
* #param string $salary
*/
public function setSalary($salary)
{
$this->salary = $salary;
}
/**
* Get salary
*
* #return string
*/
public function getSalary()
{
return $this->salary;
}
public function setCity($c)
{
$this->city = $c;
}
public function getCity()
{
return $this->city ;
}
public function __construct(){
$this->created_at = new \DateTime() ;
}
}
heres my jobType
namespace My\JobBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class JobType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title')
->add('content','textarea' )
//->add('created_at')
->add('salary')
->add('city')
//->add('announcer')
;
}
public function getName()
{
return 'my_jobbundle_jobtype';
}
}
and heres my log where i see the password updated
INSERT INTO Job (title, content, city, created_at, salary, announcer_id) VALUES (?, ?, ?, ?, ?, ?) ({"1":"lfdgdfl;","2":";lkl;fdlgkdfl;","3":"lklkl;;l","4":{"date":"2012-02-05 23:39:16","timezone_type":3,"timezone":"Europe\/Paris"},"5":"333","6":1})
UPDATE User SET password = ? WHERE id = ? ([null,1])
well i found the issue it was caused by that eraseCredential method of the UserInterface in my User entity
<?php
public function eraseCredential(){
$this->password = null ;
}
i just had to empty it as it was doin to my password by commenting that line ; ]
2 kosaidpo
Your solution works because eraseCredentials() method is used to clear user sensitive data (means NOT secret, but the one that can be restored, the sense is like __sleep()) when serializing user object or saving it to database (that is what manual says). So when you attach user to job object and call #flush(), doctrine will check for changes in all objects connected with job and find that user object has changed because eraseCredentials() has erased password. That is why your user gets updated.
There is one more solution which could help you:
The Solution:
Change Tracking Policies from Doctrine documentation.
In short, you can add #ChangeTrackingPolicy("DEFERRED_EXPLICIT") annotation (as I did, because I'm using annotations. Captain Obvious =) ) to UserInterface implementation (in my case I'm using User class) and this will tell Doctrine not to check all 'connected' to job objects.
In this case doctrine will not check user object and save it with erased password, unless you will force it to do it with calling #persist(User object) manually.
But anyway, you should not do $this->password = null in your eraseCredentials() method.
It does seem strange, but you could always retrieve User object prior to binding it to a newly created job:
$token = $this->get('security.context')->getToken();
$user_repo = $this->getDoctrine()->getRepository('**NAMESPACE**:User');
$user = $user_repo->find($token->getUser()->getId());
$job->setAnnouncer($user);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($job) ;
$em->flush();
Also, I'm not really sure but I read somewhere that token isn't supposed to carry password due to it's security nature.... maybe that is your problem...