type of users SonataUserBundle + FOSUserBundle + SonataAdminBundle - symfony

I'm trying to create 3 users in my project:
Cliente : Who will access front-end and have fields 'name', 'CPF', 'adress'.
Vendedor : Who will register Offers in the site and have fields 'phone', 'CNPJ'
Admin : Who will administer all Clientes, Vendedores, Offers, etc...
So.. I installed 3 bundles for that: SonataUserBundle + FosUserBundle + SonataAdminBundle
I followed the entire tutorial of each one. But I don't know how can I create each type of this users.
I am using ApplicationSonataUserBundle which generate entities User and Group.
Here is my code:
namespace Sete\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Application\Sonata\UserBundle\Entity\User as BaseUser;
/**
* Cliente
*
* #ORM\Table(name="cliente")
* #ORM\Entity
*
*/
class Cliente extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...another fields...
}
and Vendedor:
namespace Sete\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Application\Sonata\UserBundle\Entity\User as BaseUser;
/**
* Vendedor
*
* #ORM\Table(name="vendedor")
* #ORM\Entity
*/
class Vendedor extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
...another fields...
}
AppKernel.php
...
new Sonata\UserBundle\SonataUserBundle('FOSUserBundle'),
new Sonata\EasyExtendsBundle\SonataEasyExtendsBundle(),
new Application\Sonata\UserBundle\ApplicationSonataUserBundle()
And config.yml
...
fos_user:
db_driver: orm # can be orm or odm
firewall_name: main
user_class: Application\Sonata\UserBundle\Entity\User
group:
group_class: Application\Sonata\UserBundle\Entity\Group
sonata_user:
manager_type: orm # can be orm or mongodb
...
This way my entities Cliente and Vendedor have no association with groups. I try to add $groups relationship, but not work. So, when I try to admin this entities I got error:
An exception occurred while executing 'SELECT count(DISTINCT c0_.id)
AS sclr0 FROM cliente c0_ LEFT JOIN fos_user_user_group f3_ ON f2_.id
= f3_.user_id LEFT JOIN fos_user_group f1_ ON f1_.id = f3_.group_id':
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'f2_.id' in 'on
clause'
Is that the best pratice to create types of users? Or, Instead of extends ApplicationUserBundle:User, create Cliente and Vendedor entities (without extends ApplicationUserBundle:User), and then create relationship with User (puting fields $cliente and $vendedor inside User entity and making a relationship) ?
Sorry about the english.
I trying to do this all week. Follow many tutorials but not got the answer.
Thx all.

One way to go about having multiple user types is to use Doctrine inheritance mapping.
However, I would recommend using single user type together with role security handler. You should install SonataUserBundle with Easy Extends (installation instructions), so that you can leave SonataUserBundle unmodified. You should add all needed fields to the App\UserBundle\Entity\User and create three user Groups: Clientes, Vendedors and Admins. In your main config.yml, add two missing roles (there already is ROLE_ADMIN):
ROLE_SUPER_ADMIN: [<...>, ROLE_CLIENT, ROLE_VENDOR]
Assign those three user Groups appropriate permissions that you created.
Now, in User, make custom fieds nullable and in you UserAdmin show only the fieds user type can see using security rigths. Example:
if ($this->isGranted('ROLE_CLIENT')) {
$formMapper->add('cpf');
}

Related

Problem with auto-updating column create_at

After $this->em->flush(); updated columns create_at and update_at but need only update_at. I tried to fix trait-file with set\get but its not give result.
if $this->em->flush(); creates the entity, it will update both field otherwise it should only update updated_at field.
You can use this bundle StofDoctrineExtensionsBundle.
And activate the extensions what you want.
in yaml config you can activate timestampable.
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
timestampable: true
and you can use TimestampableEntity in your entity like this.
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\TokenRepository")
* #ORM\Table(name="tokens")
*/
class Token
{
use TimestampableEntity;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
}
so it will update and create your entities dates. :)

FOS Bundle User - multiple roles

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.

Get Entity subobject id without joining or new sql request

Example I have a Entity User which have a Contact. How I get the Contact ID without joining the contact table.
$user = $entityManager->userRepository->findUserById(1);
$contactId = $user->getContact()->getId();
when `getContact() is called the whole contact is loaded from the DB. How I can avoid this SQL request without add a join in findUserById. I only need to contactId which is in my users table, but there is no simple function like $user->getContactId().
Look at this post Getting only ID from entity relations without fetching whole object in Doctrine. I tested a simple example in a ManyToOne relation and the related object was not loaded.
Your situation sounds perfect for the Doctrine Second Level Cache
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html
With this, the first query will be gotten from DB but every subsequent query will be from the second level cache, plus if you change the data using doctrine then the cache is automatically updated. For this reason, I suggest you set a very long time for the cache my personal favourite is 1 month approx 2592000 seconds.
Enable it in your user entity like so
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* #ORM\Cache(usage="NONSTRICT_READ_WRITE", region="users_region")
* #ORM\Table(
* name="site_users",
* indexes={
* #ORM\Index(name="username_index", columns={"username"}),
* #ORM\Index(name="email_index", columns={"email"}),
* #ORM\Index(name="enabled_index", columns={"enabled"}),
* #ORM\Index(name="last_login_index", columns={"last_login"})
* }
*)
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
......
Then in your config.yml like so
orm:
auto_generate_proxy_classes: "%kernel.debug%"
entity_managers:
default:
connection: default
auto_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore
second_level_cache:
enabled: true
# region_lock_lifetime: 60
region_lifetime: 300
log_enabled: %kernel.debug%
region_cache_driver: apc
regions:
users_region:
region_lifetime: 2592000
region_cache_driver: apc
............
More information here
http://symfony.com/doc/master/bundles/DoctrineBundle/configuration.html
Note this method also ensure that the popular check for an authenticated user $user = $this->getUser(); does not hit your DB as well.
I suggest you to change fetch mode in the specific query, as described here in the doc.
So you can describe your query as follow:
// Supposing this method in the repository class
public function findUserById($idUser)
{
$qb = $this->createQueryBuilder('u')
->where("u.id = :idUser")
->setParameter("idUser", $idUser);
$query = $qb->getQuery();
// Describe here all the entity and the association name that you want to fetch eager
$query->setFetchMode("YourBundle\\Entity\\Contract", "contract", ClassMetadata::FETCH_EAGER);
...
return $qb->->getResult();
}
NB:
Changing the fetch mode during a query is only possible for one-to-one
and many-to-one relations.
Hope this help

Symfony - Doctrine not generating entity

i have a quite primitve problem:
I created an Entity in my Symfony App:
src/AppBundle/Model/Article/Article.php:
<?php
namespace AppBundle\Model\Article;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="articles")
*/
class Article {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length="255", nullable=false)
*/
private $name;
}
When i type in console:
php app/console doctrine:generate:entities AppBundle
it prints:
Bundle "AppBundle" does not contain any mapped entities.
And when i type:
php app/console doctrine:generate:entities AppBundle/Model/Article/Article
it prints:
Class "AppBundle\Model\Article\Article" is not a valid entity or mapped super class.
Anybody has some idea how to solve this?
I already followed some solutions on stackoverflow like changing or turning off cache/clearing cache/ etc.. but none works.
Thanks and Greetings!
Probably Doctrine doesn't know about these mappings.
Have you created this bundle with a generator?
Check your config.yml in app/config, you should have an entry under following keys structure:
doctrine:
orm:
mappings:
AppBundle: ~
Edit:
Ok, you use custom structure.
Default namespace should look like that:
AppBundle\Entity
So full class name should be AppBundle\Entity\Article
If you want to stick with custom mappings, check configuration docs.
You should add all your Entities in the Entity Folder, remember Entity and Model are different concepts. You can create others folders like Model, Handlers, Managers. Another important thing is not to confuse Data Base with Object-oriented programming.
You need to make sure that you do not use Doctrine\Common\Annotations\SimpleAnnotationReader class. Instead Doctrine\Common\Annotations\AnnotationReader should be used.
Simple reader doesn't support #ORM\Annotation, only #Annotation.

Symfony2 - Share Entity Between Bundles with different relationships

How do you share an entity between multiple bundles with different relationships?
For example both the ZooAnimalBundle and FarmAnimalBundle need a User Entity. A third Bundle AccountUserBundle has the User Entity.
In both the Zoo and Farm AnimalBundles I create a User Entity like so:
use Account\UserBundle\Entity\User as BaseUser;
class User extends BaseUser
{
}
I then have a Hospital entity in Zoo:
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_zoo_animals")
*/
protected $users;
And a Room entity in Farm:
class Room {
/**
* #ORM\ManyToMany(targetEntity="Farm\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_farm_animals")
*/
protected $users;
Everything works so far in that I can call Zoo.Room->getUsers() or Farm.Hospital->getUsers()
However the problem is I'm not sure on how to set up the inverse relationship in their respective User entities.
If for example I update the FarmAnimal User Entity and run doctrine:generate:entities
/**
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Room", mappedBy="users", cascade={"persist"})
*/
protected $rooms;
}
It will copy the protected $properties from BaseUser and create all the set and get methods which is not what I want. What is the correct way of setting up these relationships?
Update
If you don't setup the inverse relationship, how would you select all users where hospital.id = 1
$qb = $this->getEntityManager()->createQueryBuilder()
->select(
'u'
)
->from('Account\UserBundle\Entity\User','u')
->leftJoin('u.hospitals', 'h')
->andWhere('h.id = :hospital_id')
->setParameter('hospital_id',$hospital_id);
This gives the error:
Class Account\UserBundle\Entity\User has no association named hospitals
I know I could select from hospital and join user because that relationship does exist but I need to select users because I am using them with Doctrine\ORM\Tools\Pagination\Paginator
The query would be
$qb = $this->createQueryBuilder('a')
->select(
'h', 'u'
)
->leftJoin('h.users', 'u')
The problem with this is Paginator only sees one result Hospital because the Users are attached to it.
You can define abstract entity dependencies and implement them with other bundles.
First, each of the bundles depending on a user entity should define a User interface. For example:
namespace Foo\BarBundle\Entity;
interface UserInterface
{
public function getId();
public function getEmail();
// other getters
}
Then, in each entity depending on the user, define the relationship, e.g.:
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Something
{
/**
* #ORM\ManyToOne(targetEntity="UserInterface")
* #Assert\NotNull
*/
protected $User;
// add other fields as required
}
Now you need to register the User entity as an implementation of the UserInterfaces:
namespace Foo\UserBundle\Entity;
use Foo\BarBundle\Entity\UserInterface as BarUserInterface;
use Foo\FoobarBundle\Entity\UserInterface as FoobarUserInterface;
/**
* #ORM\Entity
*/
class User implements BarUserInterface, FoobarUserInterface
{
// implement the demanded methods
}
Then add the following to app/config/config.yml:
doctrine:
orm:
resolve_target_entities:
Foo\BarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
Foo\FooarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
(Heads up: there will usually already be a doctrine.orm node which you'll have to extend.)
This is not a perfect solution, because you cannot really say which fields the user entity should have. On the other hand, it's strictly OOP, as you don't have to know about internals of the User implementation – you just need it to return the right values.
Creating multiple definitions of the account is the wrong way to do it, unless you want to create 3 seperate user tables (even then it's better not to do it this way).
Really you want your other entities to map to the your user entity in the account bundle.
I.e.,
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AccountBundle\Entity\User")
*/
protected $users;
Now, there is no need to create the inverse relationship. In fact, this is a bad practice since you have a bi-directional dependency. Users don't know about hospitals, but hospital knows about it's users. Now, any bundle can map to the user entity and reuse it.

Resources