I'm creating a class to manage user groups. A group can contain either users or groups of users
I wonder if there is already a symfony class to implement that handle such relationships.
The best way that occurred to me is something like this:
class Group
{
/**
* #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\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="coordinator_id", referencedColumnName="id", nullable=false)
*/
private $coordinator;
/**
* #ORM\ManyToOne(targetEntity="Group")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
private $parent;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="users")
* #ORM\JoinTable(name="groups_users")
*/
private $users;
}
There is currently ( as of 2013-06-16 ) no bundle available which directly provides this special implementation.
FOSUserBundle introduces groups for roles ... but the implementation is pretty basic and needs manual hands-on to get it working fully. Maybe it's something you could look into for inspiration though.
Otherwise your approach looks okay to me for this special use-case.
You might be able to improve by using nested sets with Gedmo's Tree doctrine extension to handle the group-nesting.
Related
in Symfony (5.3.7 at present) I've got main data-entities and settings seperated. For example there is a user entity (default stuff), a TypeUserSetting defining the different settings and UserSetting which is m:n in between with the current setting stored.
namespace App\Entity;
/**
* #ORM\Entity(repositoryClass=TypeUserSettingRepository::class)
*/
class TypeUserSetting
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $description;
/**
* #ORM\OneToMany(targetEntity=UserSetting::class, mappedBy="setting")
*/
private $userSettings;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $default_value;
and
class UserSetting
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=4000, nullable=true)
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="userSettings")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity=TypeUserSetting::class, inversedBy="userSettings",cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $setting;
Thats not that complicated so far...
My problem is, that oftenly settings don't exist, because the users did not set them. In that case I want to use the default.
class User ...
/**
* #ORM\OneToMany(targetEntity=UserSetting::class, mappedBy="user", orphanRemoval=true)
*/
private $userSettings;
...
public function getSettingById(int $id):string
{
foreach ($this->getUserSettings() as $oneSetting) {
if ($oneSetting->getId() === $id)
return $oneSetting->getValue();
}
//Not set, return the default
....
}
And here we go... If there is no setting, the loop fails and I want to get the default form the coresponding TypeUserSetting. This is not mapped, so I have to get it from the database, but I didn't find a way to access that properly.
Possible solutions I found that far:
Insert the UserSetting for all users by SQL when adding a TypeUserSetting. This would avoid the whole problem, but I simply don't like that.
Adding a static method to the TypeUserSetting-repo to get the value. I think that's ugly and somehow going back to last century...
Inject the TypeUserSetting-repo by LifeCycle-hooks which (in my oppinion) isn't the way entities should be used.
Injecting the repo from the controller that calls the function... I think this would be the opposite of encapsulation and separation of concerns. (and I think about hitting my head to the wall, just for having this kind of thoughts)
Anybody got a good idea to solve that?
Thanks in advance
I have the following code in Symfony 3:
A class Appointment
<?php
/**
* Appointment
*
* #ORM\Entity
* #ORM\Table(name="ev_appointment")
*/
class Appointment
{
/**
* #ORM\OneToMany(targetEntity="EmailForward", mappedBy="_appointment")
*/
private $_email_forwards;
/**
* #ORM\OneToMany(targetEntity="ParticipationRequest", mappedBy="_appointment")
*/
private $_participation_requests;
}
A class EmailForward
<?php
/**
* #ORM\Entity
* #ORM\Table(name="ev_email_forward")
*/
class EmailForward
{
/**
* #ORM\ManyToOne(targetEntity="Appointment" , inversedBy="_email_forwards")
* #ORM\JoinColumn(name="ev_appointment_id", referencedColumnName="id")
*/
private $_appointment;
/**
* #ORM\Column(type="string", length=255, name="email", nullable=true)
*/
private $_email;
/**
* #ORM\Column(type="datetime", name="forwarded_at", nullable=true)
*/
private $_forwarded_at;
/**
* #ORM\Column(type="string", length=255, name="source", nullable=true)
*/
private $_source;
}
A class ParticipationRequest
<?php
/**
* #ORM\Entity
* #ORM\Table(name="ev_participation_request")
*/
class ParticipationRequest
{
/**
* #ORM\ManyToOne(targetEntity="Appointment", inversedBy="_participation_requests")
* #ORM\JoinColumn(name="ev_appointment_id", referencedColumnName="id")
*/
private $_appointment;
/**
* #ORM\Column(type="string", length=255, name="email", nullable=true)
*/
private $_email;
/**
* #ORM\Column(type="datetime", name="forwarded_at", nullable=true)
*/
private $_forwarded_at;
/**
* #ORM\Column(type="string", length=255, name="source", nullable=true)
*/
private $_source;
}
Now seems to me like I have 2 relationships with 2 Entities that have the exact same structure. So I am wondering, what is the right way to go?
On the one hand I could leave it as it is, because it does, work. But again, if some information was to be the same in both fields, it seems kinda like a waste to have 2 DB entries with the exact same info, and also harder to mantain afterwards.
Is there a more intelligent approach to solve this issue?
You could use e.g. Single Table Inheritance.
By that you only define the structure for EmailForward and ParticipationRequest once and all data will be persisted in one table in the database. During ORM mapping Doctrine will recognize which type you're using and instantiate the correct Object for you.
I don't see how to solve the 'if data is same in both relations it will be peristed twice' because
if it was always the same you wouldn't need two relations
there is no real way to keep it in one persistence - only option I see would be to create another relation from EmailForward and ParticipationRequest which keeps the data which might be needed twice and is referenced from both Objects then.
I have a table which store a tree for each username.
My entity looks like this:
/**
* Confsaves
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="confsaves")
*#ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
*/
class Confsaves
{
/**
* #var string
*
* #ORM\Column(name="Name", type="string", length=200, nullable=true)
*/
private $name;
/**
* #var string
*
* #Gedmo\TreeRoot
* #ORM\Column(name="Username", type="string", length=50, nullable=true)
*/
private $username;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Confsaves", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE", nullable=true)
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Confsaves", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
/**
* #var integer
*
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer", nullable=true)
*/
private $lft;
/**
* #var integer
*
* #Gedmo\TreeRight
* #ORM\Column(name="rght", type="integer", nullable=true)
*/
private $rght;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
I want build a different tree for each user.
In my controller I have created the repository like this:
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('MyBundle:Confsaves');
How can I set the scope of the repository only on the user connected?
Is level necessary for build tree function?
I use an existing database that only have left, right and parent arguments.
Has been a long time, but for the record:
If you want a tree for each user, you must establish a relationship between your Confsaves and User entities, more specifically, between the root property of your Confsaves entity and your User one. Doctrine Extensions supports relationships on the root property of your tree since 2.4. Make sure you are using the right version. Cost me a day of debugging.
So, to relate your nested set tree to a user entity, just perform a ManyToOne in your User, and the inverse relationship in your Tree entity.
Very very weird. I have used this method from doctrine hundreds of times. I have a simple controller that takes an id as parameter. The query that Doctrine generates is wrong and crash.
/**
* #Security("has_role('ROLE_ADMIN')")
* #return Response
*/
public function editSellerAction($id)
{
$em = $this->getDoctrine()->getManager();
$seller = $em->getRepository('SiteUserBundle:Seller')->find($id);
// ...
$form = $this->createForm(new SellerType(), $seller, array(
'method' => 'POST'
));
// ...
}
The query generated is the following
[2/2] DBALException: An exception occurred while executing 'SELECT t1.id AS id2, t1.username AS username3, t1.password AS password4, t1.firstname AS firstname5, t1.lastname AS lastname6 FROM seller t1 WHERE t0.id = ? LIMIT 1' with params ["2"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.id' in 'where clause' +
The error thrown makes sense because it's looking at "WHERE t0.id" when it should be looking at "WHERE t1.id". I tried the query with t1 using phpmyadmin and it works.
Any idea what might cause this issue?
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
* #author Michael Villeneuve
*/
class Seller extends User
{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I have 3 entities that extends the User (customer, admin and seller).
Updated link: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html
Read up a bit on mapped super classes: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html. Basically, your abstract base user class cannot itself be an entity.
So take the #ORM\Entity line out of your User class. That is where the table 0 (t0) is coming from.
You have 2 options:
The first one is to create an abstract User entity and inherit all values from it. This is useful if you have many entities with the same behaviour. I e.g. like to create a BaseEntity with a ID field and some basic methods. All entities can extend this one and automatically have an ID. Cerad explained in his answer how this is done.
The second option are so called discriminator fields. Basically they allow you to have one User table and sub-tables for every extended entity. You can read about them in the official docs.
Which one you end up using is probably case dependent.
Try to add id field to the Seller entity instead of User
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
*/
class Seller extends User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
* #author Michael Villeneuve<michael#panierdachat.com>
*/
class User implements UserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I have several bundles in my app and I would like to have relations between tables.
One is my User(StoreOwner) which is in UserBundle, and the second is Store in StoreBundle.
The relation between them is OneToMany (User -> is owner of -> Store).
Store
/**
* Description of Store
*
* #ORM\Table(name="Store")
* #ORM\Entity(repositoryClass="Traffic\StoreBundle\Repository\StoreRepository")
* #author bart
*/
class Store extends StoreModel {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $name
*
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(
* message="Please provide your shop name"
* )
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\StoreOwner", inversedBy="stores")
*
*/
protected $owner;
}
StoreOwner
/**
* #ORM\Entity
*
*/
class StoreOwner extends User implements StoreOwnerInterface {
/**
* #var type ArrayCollection()
*
* #ORM\OneToMany(targetEntity="Traffic\StoreBundle\Entity\Store", mappedBy="owner", cascade={"persist"})
*/
protected $stores;
}
My question is:
Is there any solution to avoid dependency between StoreBundle and UserBundle and keep relations between Entities in Doctrine?
This is a valid concern in my opinion. Two-way dependencies between bundles are a smell.
One way of solving the dependency issue is moving your entities out of the bundles into a more general namespace. This way both bundles will depend on the same "library" but won't depend on each other directly.
I recently wrote a blog post on how to do it: How to store Doctrine entities outside of a Symfony bundle?