In my web application, I want my user's to be able to create roles and add users to them dynamically. The only thing I imagine, is to edit the security.yml every time, but this can't be the best solution, can it? It would be very nice, if there is something like a User Provider for roles, so I can define one which loads the roles from a database (doctrine).
Thanks for your help, hice3000.
Then, you should want to add a role Entity to your model Hice.
You have to know that Symfony2 provides support for dynamic roles too. You have a getRoles() method in the Symfony2 User spec in the API Doc, that your User entity should implement, that forces him to return Roles. These roles must either implement the role interface that specifies a getRole() method that returns, most usually, the role name itself.
You can then add the newly created role directly to your user role list that the getRoles() user method will then return.
Here is an example using annotations :
First role class
/**
* Role class
*
* #ORM\Entity()
*/
class Role implements RoleInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="userRoles")
*/
private $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRole()
{
return $this->name;
}
}
And the User class
/**
* User
*
* #ORM\Entity()
*/
class User implements UserInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255)
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $userRoles;
public function __construct()
{
$this->userRoles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRoles()
{
return $this->userRoles->toArray();
}
I've skipped imports and methods to simplify the approach.
EDIT : There is something to know about serialization too. As Sharom commented on github, you musn't serialize users in roles neither roles in users. Just read his post and I think you'll understand :)
Related
Simple example:
I've got two users Admin and Client (both implements UserInterface) and Cart - three entity classes at a. Admin and Client can have his own carts. How to configure/resolve Cart entity relation to have method 'getUser()' which returns Admin or Client user?
Maybe I can have column user_id and second column with user entity name in Cart (something similar as DiscriminatorMapping can do)?
class Admin implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Cart", mappedBy="???")
*/
private $carts;
....
class Client implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Cart", mappedBy="???")
*/
private $carts;
....
class Cart
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var UserInterface
* #ORM\ManyToOne(targetEntity="UserInterface", ???)
*/
private $user;
....
I tried this Doctrine feature, also tried DisciminatorMapping and composite keys (join by multiple columns) option with no luck.
Any help?
I think you were almost there. DiscriminatorMapping is probably the way to go. However, you need to bind Client and Admin to a Parent class. So consider this hierarchy:
User (parent class)
Admin (extends User)
Client (extends User)
Then in your Cart entity you bind the relation to the User entity.
I have this entity
/**
* #ApiResource()
* #ORM\Entity(repositoryClass="App\Repository\FeedRepository")
*/
class Feed implements AuthoredEntityInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="myFeeds")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="string", length=2083, unique=true)
*/
private $url;
// various getters and setters
}
using the AuthoredEntityInterface I made I can automatically set the user to the logged user.
I'd need to know how to set the collectionOperations so when I am logged in as the user with id = 1, when I call /api/feeds I will only retrieve items with user = 1. If this is possible I would like to do this with an annotation, otherwise any other method is ok.
thanks.
If it is just for connected user, what you need is a current user extension (doctrine extension). Else, you need to create a "subresource' link.
Link to Extension, and to Subresource.
Enjoy :) (and thank you timisorean for the review)
I'm trying to use the JMSSerializer with Symfony to build a simple json api.
So i have 2 simple Entities (1 User can have many Cars, each Car belongs to one User):
class Car
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cars")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
}
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Car", mappedBy="user", orphanRemoval=true)
*/
private $cars;
}
Now i want to get all Cars with their User.
My Controller:
class CarController extends AbstractController
{
/**
* #param CarRepository $carRepository
*
* #Route("/", name="car_index", methods="GET")
*
* #return Response
*/
public function index(CarRepository $carRepository)
{
$cars = $carRepository->findAll();
$serializedEntity = $this->container->get('serializer')->serialize($cars, 'json');
return new Response($serializedEntity);
}
}
This will throw a 500 error:
A circular reference has been detected when serializing the object of
class \"App\Entity\Car\" (configured limit: 1)
Ok, sounds clear. JMS is trying to get each car with the user, and go to the cars and user ....
So my question is: How to prevent this behaviour? I just want all cars with their user, and after this, the iteration should be stopped.
You need to add max depth checks to prevent circular references.
This can be found in the documentation here
Basically you add the #MaxDepth(1) annotation or configure max_depth if you're using XML/YML configuration. Then serialize like this:
use JMS\Serializer\SerializationContext;
$serializer->serialize(
$data,
'json',
SerializationContext::create()->enableMaxDepthChecks()
);
Example Car class with MaxDepth annotation:
class Car
{
/**
* #\JMS\Serializer\Annotation\MaxDepth(1)
*
* [..]
*/
private $user;
I have two roles in my application, for example ROLE_USER and ROLE_SUPERUSER. Users are stored in the database using Doctrine. Users with the ROLE_USER role are based on a simple User class. (Getters and setters have been removed for readability.)
Acme\MyBundle\Entity\User.php
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="Acme\MyBundle\Entity\UserRepository")
* #ORM\Table("users")
* #UniqueEntity(
* fields={"email"},
* message="email already used"
* )
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
protected $email;
/**
* #ORM\Column(type="string", length=32)
*/
private $salt;
/**
* #ORM\Column(type="string", length=4096)
*/
private $password;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct()
{
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
/**
* #inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER','ROLE_SUPERUSER');
}
}
I have a SuperUser class that extends User to provides more fields that only users with ROLE_SUPERUSER would need.
Acme\MyBundle\Entity\SuperUser.php
namespace Acme\MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="super_users")
*/
class SuperUser extends User
{
/**
* #var integer
*/
protected $id;
/**
* #var string
*/
protected $email;
/**
* #ORM\Column(type="string", length=32)
*/
proteced $avatar;
/**
* #ORM\Column(type="string", length=50, unique=true)
*/
proteced $username;
}
In my controllers I'm using $user = $this -> getUser(); to get the current user, but this returns an instance of the User class and I cannot access the SuperUser properties or methods, even if the user has the role ROLE_SUPERUSER.
For example, I would like to be able to use the following code.
if ($this->get('security.context')->isGranted('ROLE_SUPERUSER')) {
$avatar = $this->getUser()-> avatar;
}
Is there anyway to be able to do that? I would say there's something to do with Doctrine relationships, but I don't really know what to change.
By the way, as you can see I don't have an username field in my standard User class, it's only present in SuperUser. Does this may cause any problems since the authentication is based on username?
I think my problem hasn't been clearly exposed, but this might be due to my current code, which is wrong.
I don't have two user tables. I want only one User class (with one users table). The authentication is operated only on this class with email and password.
I have another class SuperUser```that provides extra fields to users that have the ROLE_SUPERUSER`` role, but the superusers are users and have an entry in the users table. I just want to create a left join on the concerned rows, that's why I used inheritance. (Maybe there's a better way to do it.)
If I want to get all the emails, I can query the users table. If I want to get all the usernames, since only superusers have one, I can query the superusers table.
I would go for two entities with a OneToOne relationship (No extends)
/** #Entity **/
class SuperUser
{
// ...
/**
* #ORM\OneToOne(targetEntity="User", mappedBy="superUser")
**/
private $user;
// ...
}
/** #Entity **/
class User
{
// ...
/**
* #ORM\OneToOne(targetEntity="SuperUser", inversedBy="user")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
**/
private $superUser;
// ...
}
Otherwise take a look at Inheritance Mapping
You should have two user providers then, and use one master provider:
security:
providers:
master_provider:
chain:
providers: [user, super_user]
user:
entity: { class: Acme\MyBundle\Entity\User, property: username }
super_user:
entity: { class: Acme\MyBundle\Entity\SuperUser, property: username }
firewalls:
main:
provider: master_provider
http://symfony.com/doc/current/cookbook/security/multiple_user_providers.html
I'm trying to setup a many to many between fos Userbundle and my own group bundle so that I can group users. this is working fine. I can set a new group and can add as many users to this group as I like to. But when I want to check if a user is in a group, I get a Index join Column error. I think I didn't understand the usage of manytomany the correct way so it would be nice if you can help me getting the point.
My entities look like:
User:
class User extends BaseUser
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $usergroups;
//....//
And my Group Entity looks like:
/**
* #ORM\ManyToMany(targetEntity="PrUserBundle\Entity\User", inversedBy="id")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
* #var user
*/
private $user;
//....
/**
* Add user
*
* #param \PrUserBundle\Entity\User $user
* #return Lieferanten
*/
public function addUser(\PrUserBundle\Entity\User $user)
{
$this->user[] = $user;
return $this;
}
/**
* Remove user
*
* #param \PrUserBundle\Entity\User $user
*/
public function removeUser(\PrUserBundle\Entity\User $user)
{
$this->user->removeElement($user);
}
/**
* Get user
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getUser()
{
return $this->user;
}
When I try to catch all users in a group, I get an error:
$group=$em->getRepository('PrGroupBundle:Group')->findAll();
var_dump($lfr[0]->getUser()->getId());
I guess I missunderstood how to handle the bidirectional manytomany. Or can I use a manytoone also?