FOS Bundle User - multiple roles - symfony

I'm exploring the possibilities of FOS Bundle User.
Thanks to Knpuniversity (https://knpuniversity.com/screencast/fosuserbundle/roles-canonical-fields) I discovered this good bundle to manage users.
In my case I also need multiple roles BUUUUUUT I don't want to save it with an array of roles in a field from user table. My intention is to use more tables with a relation 'n' to 'm' where 1 user have 'n' roles and 1 role can be used by 'n' users also.
So instead of having one table to manage users and roles, I will need one table for users, one table for roles and a last one to make the relationship n-m between them. I need that because I will use this structure for others functionalities and saving the roles as an array... will create more problems than solve... in my case.
What you propose to achieve that?
Perhaps another bundle?
Perhaps a simple solution to adapt the bundle to my requirements?
What do you think I could do?

With the FOSUserBundle indeed you can manage all kind of roles through the groups class that this bundle creates.
In the normal flow from symfony these groups they doesn't exist by default. Its an extra feature that provides this bundle, but it helps you to manage better the roles for the users.

This is what I am doing and works pretty well for me:
I have a User,Role and a Group entities
User Entity
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
Group Entity
use FOS\UserBundle\Model\Group as BaseGroup;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_group")
*/
class Group extends BaseGroup
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
all this is from FOS docs
Now the roles by default comes as array but symfony provides a Symfony\Component\Security\Core\Role\Role class to extend from it.
so your role class should look like this:
Role Entity
use Symfony\Component\Security\Core\Role\Role as BaseRol;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_role")
*/
class Role extends BaseRol
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
To do the magic you just need to add the mapping information as follow.
To support groups in users entities
/**
* #ORM\ManyToMany(targetEntity="path/to/my/groupEntity")
* #ORM\JoinTable(name="join_table_name",
* joinColumns={#ORM\JoinColumn(name="join_column_name_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="join_column_name_id", referencedColumnName="id")}
* )
*/
protected $groups;
To support roles in both Group and User entity
/**
* #ORM\ManyToMany(targetEntity="path/to/my/roleEntity")
* #ORM\JoinTable(name="join_table_name",
* joinColumns={#ORM\JoinColumn(name="join_column_name_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="join_column_name_id", referencedColumnName="id")}
* )
*/
protected $roles;
After that update your database and you are ready to go.

Finally what I did is use the functionality from the FOSUserBunlde using roles and groups where each role is used as permissions to execute controller and the groups to manage easily all the roles from a user.

I would create a new UserManager class extending the FosUserManager class overriding the role related methods. But not sure about if it's worthy or not. Also you will have to create a middle plain object that extends from the FosUserBundle User model and then use it to create yor user Entity. In this middle object you will have to change the way Roles are stored.
Seriously I'm not sure of this is better than starting your own user management from scratch.

Related

Check whether a newly created object is unique by several fields

I have to check a submitted form against the existing database to make sure it is not duplicated by the combination of several fields, say supplier_id, invoice_no and amount.
Is there a built-in method or should I write a code myself? If so - what are guidelines: where to put it, what are the good practices?
At the moment I am extending the CRUD controller and overwriting the createAction adding the condition there. Not sure whether this method is a good practice.
Example:
<?php
namespace AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity({"name", "email"}, message="This value is in a database.")
*/
class User
{
/**
* #var string
*/
protected $name;
/*
* #var string
*/
protected $email;
...
}

Doctrine Entity Repository how to add 'andWhere' to all 'find*' functions?

For legacy reasons I have table that is used for many purposes. Only subset of rows is relevant to Entity I'm writing. Criteria is simple, just check 'project' field for specific value.
Instead of reimplementing find, findAll, findByName, findByID, findBy.... Just notify doctrine to append single condition to them all. Is that possible without reimplementing each and every find* ?
Or maybe it can be done on lover level still?
UPDATE:
Reworked question, to specify what kind of solution would be acceptable.
An available easy-to-use solution is to create a Repository with your custom find function.
Then, if all your entities has a specific Repository, make them (Repository) extending from yours (which contains the custom find method), otherwise (you doesn't have a Repository per entity), assign the repository to all your entities with the repositoryClass option of the #ORM\Entity annotation like :
#ORM\Entity(repositoryClass="YourMainRepository")
Otherwise, if you doesn't want put any repository in your entities, override the whole default repository and customise the find method.
I already used the last option because of a specific need, also I invite you to see the following question :
Abstract repository and #Entity annotation inheritance
Look at the solution wich contains a gist of all required steps for override the default doctrine repository.
See Custom Repository classes
Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Phrase
*
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
.............................................................
.............................................................
.............................................................
Your Repository:
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/** For example **/
public function getByName($name)
{
$qb = $this->createQueryBuilder('u')
->where('u.name = :name')->setParameter('name', $name)
->andWhere('u.lastname LIKE :name')->setParameter('lastname', '%'.$name.'%');
$query = $qb->getQuery();
return $query->getResult();
}
}
In Your Controller:
/**
* #Route("/", name="index")
*/
public function indexAction(Request $request)
{
$userRepository = $this->getDoctrine()->getRepository('AppBundle:User');
$userName = $userRepository->getByName($name);
..................................................................................
..................................................................................

JMS Serializer: How to limit the depth of serialisation for an object graph

Maybe it is just my misunderstanding of this annotation however it does not seam to work as expected.
I have the following object graph
User
-> Company
-> Users
-> Groups
-> Permissions
As you can see there will be some recursion. JMS handles this quite well by not serialising the other user's company properties as well as not the current user.
However I want the serialization to stop at and include company.
I have tried this expecting that once the level $context->level = 2 it would stop
<?php
namespace FinalConcept\TimeTracker\EntitiesBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* FinalConcept\TimeTracker\EntitiesBundle\Entity
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="FinalConcept\TimeTracker\EntitiesBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\ManyToOne(targetEntity="company", inversedBy="users")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* #JMS\MaxDepth(depth=1)
*/
private $company;
}
However this is not the case. Even stepping through the code has not shed any light on how to stop this.
I am happy to create a custom handler if I can only invoke it for a specific path i.e. User.Company
I also need this for User.Groups which has the following graph
User
-> Groups
-> Permissions
-> Users
-> Groups
-> users ....
Thanks in advance for any help how to limit the depth of serialization for an object graph
Because I did not have access to the latest version of the serializer I had to find a workaround to the #MaxDepth. This may also help you.
Use #JMS\ExclusionPolicy("all") on all entities that are connected.
Now use #JMS\Expose only on those properties that you want to have serialize.
On join relations only use this annotation in one direction. That will fix your problem.
namespace FinalConcept\TimeTracker\EntitiesBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* FinalConcept\TimeTracker\EntitiesBundle\Entity
*
* #JMS\ExclusionPolicy("all")
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="FinalConcept\TimeTracker\EntitiesBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #JMS\Expose
* #ORM\ManyToOne(targetEntity="company", inversedBy="users")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
*/
private $company;
}
As of the latest version, using #MaxDepth() annotation and SerializationContext::create()->enableMaxDepthChecks() in the controller does the job.

Doctrine2 doesn't load Collection until a method is called

When does Doctrine2 loads the ArrayCollection?
Until I call a method, like count or getValues, I have no data
Here is my case. I have a Delegation entity with OneToMany (bidirectional) relation to a Promotion Entity, like this:
Promotion.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Promotion
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Delegation", inversedBy="promotions", cascade={"persist"})
* #ORM\JoinColumn(name="delegation_id", referencedColumnName="id")
*/
protected $delegation;
}
Delegation.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Delegation
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Promotion", mappedBy="delegation", cascade={"all"}, orphanRemoval=true)
*/
public $promotions;
public function __construct() {
$this->promotions = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Now I do something like the following (with a given delegation)
$promotion = new Promotion();
$promotion = new Promotion();
$promotion->setDelegation($delegation);
$delegation->addPromotion($promotion);
$em->persist($promotion);
$em->flush();
Looking for the relation into the database is ok. I have my promotion row with the delegation_id set correctly.
And now my problem comes: if I ask for $delegation->getPromotions() I get an empty PersistenCollection, but if I ask for a method of the collection, like $delegation->getPromotions()->count(), everything is ok from here. I get the number correctly. Asking now for $delegation->getPromotions() after that I get the PersistenCollection correctly as well.Why is this happening? When does Doctrine2 loads the Collection?
Example:
$delegation = $em->getRepository('Bundle:Delegation')->findOneById(1);
var_dump($delegation->getPromotions()); //empty
var_dump($delegation->getPromotions()->count()); //1
var_dump($delegation->getPromotions()); //collection with 1 promotion
I could ask directly for promotions->getValues(), and get it ok, but I'd like to know what is happening and how to fix it.
As flu explains here Doctrine2 uses Proxy classes for lazy loading almost everywhere. But acessing $delegation->getPromotions() should automatically invoke the corresponding fetch. A var_dump get an empty collection, but using it- in a foreach statement, for example- it is working ok.
Calling $delegation->getPromotions() only retrieves the un-initialized Doctrine\ORM\PersistentCollection object. That object is not part of the proxy (if the loaded entity is a proxy).
Please refer to the API of Doctrine\ORM\PersistentCollection to see how this works.
Basically, the collection itself is again a proxy (value holder in this case) of a real wrapped ArrayCollection that remains empty until any method on the PersistentCollection is called. Also, the ORM tries to optimize cases where your collection is marked as EXTRA_LAZY so that it is not loaded even when you apply some particular operations to it (like removing or adding an item).

How do you extend an entity in Symfony2 like you used to be able to in Symfony 1?

In older versions of Symfony you used to be able to build new objects within a data object by extending a model class with an extended subclass.
For example, I had a questionnaire model that had a results table. That results table had a Result.php model class that used to set and get the results through Doctrine. I then used the ResultPeer.php model subclass to add a new function to the Result object that took the result and depending on a fixed set of thresholds calculated a score and corresponding colour.
In the new Symfony2 version using Doctrine2 I am struggling to work out the best way to do this. When creating an entity I can only find in the documentation the ability to add objects based on the data structure relationships.
I looked at the entity repositories, but that does not appear to extend or add functionality to an original object. It seems to bring back data objects based on queries that are more complex than the standard query functions.
I also looked at services, which I can use to collect the object and then using the object create a new array that includes this object and the newly created data, but this just does not seem right or follow the philosophy that Symfony is all about.
Does anyone know how functions can be added to an existing data object. I found it really useful in the older version of Symfony, but cannot seem to find the alternative in the new version of Symfony2.
Extending an entity is the way to go. In the Doctrine2 world, they talk about inheritance mapping. Here a code example. It defines a BaseEntity then it is extendsed to create a BaseAuditableEntity and finally there is a User entity extending BaseAuditableEntity. The trick is to use the #Orm\MappedSuperclass annotation. This inheritance scheme will create a single table even if there is three entities in my relationships graph. This will then merge all properties into a single table. The table created will contains every property mapped through the relations, i.e. properties from BaseAuditableEntity and from User. Here the code examples:
Acme\WebsiteBundle\Entity\BaseEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseEntity {
}
Acme\WebsiteBundle\Entity\BaseAuditableEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseAuditableEntity extends BaseEntity {
private $createdBy;
/**
* #Orm\Column(type="datetime", name="created_at")
*/
private $createdAt;
/**
* #Orm\ManyToOne(targetEntity="User")
* #Orm\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #Orm\Column(type="datetime", name="updated_at")
*/
private $updatedAt;
// Setters and getters here
}
Acme\WebsiteBundle\Entity\User.php
namespace Acme\WebsiteBundle\Entity;
use Acme\WebsiteBundle\Entity\BaseAuditableEntity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\Entity(repositoryClass="Acme\WebsiteBundle\Entity\Repository\UserRepository")
* #Orm\Table(name="acme_user")
*/
class User extends BaseAuditableEntity implements AdvancedUserInterface, \Serializable
{
/**
* #Orm\Id
* #Orm\Column(type="integer")
* #Orm\GeneratedValue
*/
private $id;
/**
* #Orm\Column(type="string", name="first_name")
*/
private $firstName;
/**
* #Orm\Column(type="string", name="last_name")
*/
private $lastName;
/**
* #Orm\Column(type="string", unique="true")
*/
private $email;
// Other properties
// Constructor
// Setters and getters
}
Here a link to the official inheritance mapping documentation of Doctrine 2.1: here
Hope this helps, don't hesitate to comment if you need more information.
Regards,
Matt

Resources