Symfony 3 - Multiple relationships to the same Data Model - symfony

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.

Related

Symfony/Doctrine: Accessing unmapped(?) data within entity

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

Some OneToMany relationships are returned as objects and some as IRIs - API-Platform

I would like to include related questions and question groups as list of IRIs when I make get request to Form.
I have few entities:
Form (fields: question_groups, questions)
QuestionGroup (fields: form, questions)
Question (fields: form, question_group)
When I make get request for the form, it returns:
Form
questions ['/api/questions/1',...]
questionGroups [{id:1,name:'foo',questions: ['/api/questions/1',...]...},...]
As you can see, Form.questions is list of IRIs but Form.questionGroups is list of objects. I would like to have both of them as IRIs.
On the image below under questionGroups field there is a questionGroup field but there is no question field under questions or answer under answers,...
The whole thing makes no sense to me, I have tried to set #MaxDepth, it changed nothing (except when i have used #MaxDepth(0) which threw 500 error without any error massage in response or in php log)
Can anyone explain why, and what should I do to load both questions and questionGroups as list of IRIs?
Thank you
Here are relevant parts of entities mentioned above.
/**
* #ORM\Entity
* #ApiResource
* #ORM\HasLifecycleCallbacks
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class Form
{
/**
* #ORM\Column(type="text", length=200, nullable=false)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="QuestionGroup", mappedBy="form", cascade={"REMOVE"})
*/
private $question_groups;
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="form", cascade={"REMOVE"})
*/
private $questions;
}
/**
* #ORM\Entity
* #ApiResource
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class QuestionGroup
{
/**
* #ORM\Column(type="string", length=200, nullable=false)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Form",inversedBy="question_groups")
*/
private $form;
/**
* #ORM\OneToMany(targetEntity="Question", mappedBy="question_group", cascade={"REMOVE"})
*/
private $questions;
}
/**
* #ORM\Entity
* #ApiResource
* #Gedmo\SoftDeleteable(fieldName="deleted_at", timeAware=false, hardDelete=true)
*/
class Question
{
/**
* #ORM\Column(type="string", length=200, nullable=false)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Form",inversedBy="questions")
*/
private $form;
/**
* #ORM\ManyToOne(targetEntity="QuestionGroup", inversedBy="questions")
*/
private $question_group;
}
I have figured out that if Entity field/property is snake_case like "$question_groups" then API will return it as list of objects and if its camelCase like "$questionGroups" it will be returned as list of IRIs. Side note: normalisationContext does not like snake_case. if you use normalisationContext and the property is snake_cased than it will be not included in the response data.

How to use Gedmo nested tree for storing multiple trees in one single table?

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.

Doctrine query crashing

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;

Class for manage users groups

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.

Resources