I have two Entities:
User and
Comment
Every comment can only have one user but a user can have multiple comments.
I'm not sure if I want to use biderectional, Unidirectional with Join Table, or Self-referencing.
I only want this relationship to apply when calling the comment object. If I call a user object somewhere I do not want a bunch of comment objects flooding the user object. Which approach should I take?
Symfony's documentation explains the process pretty well : http://symfony.com/doc/current/book/doctrine.html#fetching-related-objects
What's important is the fact that you have easy access to the
product's related category, but the category data isn't actually
retrieved until you ask for the category (i.e. it's "lazily loaded").
Just make a ManyToOne relationship, and only fetch a user's comments when you need to.
User entity:
<?php
class User
{
// ...
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="author")
*/
protected $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
}
Comment entity:
<?php
class Comment
{
// ...
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="comments")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $author;
}
Related
I have an entity User with lots of feature built for it.
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
}
This entity is good as is for most of my User.
However, a few of them will have a special ROLE, ROLE_TEACHER.
With this role, I need to store a lot of new variables specially for them.
If I create a new entity Teacher, doctrine creates a new table with every User's data + the Teacher's data.
/**
* #ORM\Entity(repositoryClass="App\Repository\TeacherRepository")
* #Table(name="teachers")
*/
class Teacher extends User
{
/**
* #ORM\Column(type="string", length=64, nullable=true)
*/
protected $test;
public function __construct()
{
parent::__construct();
}
}
What I want, is for Teacher & User to share the users table and have the teachers table only store the extra data. How could I achieve that ?
This is more of system design problem than implementation problem. as #Gary suggested you can make use of Inheritance Mapping which can have Performance issues, I'd rather suggest re think your schema and make use of database normalization techniques to break up your data into more manageable entities.
You can have User entity :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("email", message="Email already in use")
* #ORM\HasLifecycleCallbacks
* #Table(name="users")
*/
class User implements UserInterface
{
/* variables + getter & setter */
/**
* One user has many attibute data. This is the inverse side.
* #OneToMany(targetEntity="UserData", mappedBy="data")
*/
private $data;
}
With other UserData Entity with OneToMany relationship :
/**
* #ORM\Entity(repositoryClass="App\Repository\UserDataRepository")
* #Table(name="user_data")
*/
class UserData
{
/* variables + getter & setter */
#ORM\Id()
private $id;
/**
* Many features have one product. This is the owning side.
* #ManyToOne(targetEntity="User", inversedBy="data")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\Column(type="string")
*/
private $attribute;
/*
* #ORM\Column(name="value", type="object")
*/
private $value;
}
Now you can have list of user attributes without requiring specific structure to each role. It's scalable and arbitrary.
You can also define same Relation with TeacherData, StudentData or UserProfile Entities with foreign keys and branch your application logic according to the roles. Key is to break data into their separate domains and keep common data in one table. Load related data by querying related entity, this increases readability and makes it easy to break complex structure into manageable codebase.
I have two entities, User and Notification. In each notification, there is a sender and receiver that are both User entities. But doctrine doesn't like it. The schema validation says:
The mappings ACME\CoreBundle\Entity\Notifications#sender and ACME\CoreBundle\Entity\User#notifications are inconsistent with each other.
Here are the mappings for both entities:
/**
* Notifications
*
* #ORM\Table(name="notifications")
*
*/
class Notifications
{
/**
* #ORM\ManyToOne(targetEntity="WD\UserBundle\Entity\User", inversedBy="notifications")
*/
protected $sender;
/**
* #ORM\ManyToOne(targetEntity="WD\UserBundle\Entity\User", inversedBy="notifications")
*/
protected $receiver;
}
And the User one:
/**
* User
*
* #ORM\Table(name="My_user")
*
*/
class User extends BaseUser
{
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="WD\CoreBundle\Entity\Notifications", mappedBy="receiver")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $notifications;
}
For readability reasons, I did not put the whole entities code, but I believe these should be enough info.
I believe the error comes from the fact I cannot put two 'mappedBy" values in User entity, but I'm not sure. And if it is, then I have no idea how to fix this.
I've found kinda similar cases on this website, but none that was exactly like mine (or I haven't found them).
Any idea how I could fix this?
I think the issue might be that you're having two properties (sender, receiver) and using the same column to map them. If you need to distinguish between sent and received, you'll need to have sender and receiver properties on Notification and then in your user have sentNotifications and receivedNotifications. You can combine them in an un-mapped method in your User if you do need to get everything together in one call such as:
/**
* #var Notification[]|ArrayCollection
*/
public function getAllNotifications()
{
return new ArrayCollection(
array_merge(
$this->sentNotifications->toArray(),
$this->receivedNotifications->toArray()
)
);
}
I have a Person entity and an Address entity, set up with a bi-directional one-to-one relationship, with the FK on Address:
...
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person")
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
The Address Entity does NOT have a dedicated primary key, it instead derives its identity through the foreign key relationship with Person, as explained here
The form I have for creating a new Person also embeds the form for Address. When the form is submitted, this controller action is executed:
public function createAction(Request $request)
{
$person = new Person();
$form = $this->createCreateForm($person);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
return $this->redirect($this->generateUrl('people'));
}
return array(
'person' => $person,
'form' => $form->createView(),
);
}
I've inspected the data, and $person has its $address property filled out with the proper form details as expected. However, once the $person object is persisted, I get the error:
A new entity was found through the relationship
'Acme\AcmeBundle\Entity\Person#address' that was not configured to
cascade persist operations for entity...
I've tried a couple of things and none seem to work:
Setting cascade={"persist"} on the OneToOne relationship definition on the Person object. Doing so results in error:
Entity of type Acme\AcmeBundle\Entity\Address is missing an assigned
ID for field 'person'...
On the Person#setAddress method, I've taken the $address parameter and manually called $address->setPerson($this) on it. Doesn't work either.
It seems like my problem is that Doctrine is trying to save the Address object before saving the Person object, and it can't because it needs to know the ID of the associated Person first.
For instance, If I alter the the persist code to something like this, it works:
...
// Pull out the address data and remove it from the Person object
$address = $person->getAddress();
$person->setAddress(null);
// Save the person object and flush so we get an ID
$em->persist($person);
$em->flush();
// Now set the person object on the address and save the address
$address->setPerson($person);
$em->persist($address);
$em->flush();
...
How can I do this properly? I want to retain the ability to embed forms with this type of one-to-one relationship, but things are starting to get complicated. How do I get Doctrine to flush the $person object before flushing the $address object, without manually doing it myself like above?
Keep the cascade=persist.
Then modify Person::setAddress
class Person
{
public function setAddress($address)
{
$this->address = $address;
$address->setPerson($this); //*** This is what you are missing ***
This is a very common question but it's hard to search for.
Your mapping is incorrect. You are using #ORM\Id incorrectly on $person.
If you haven't yet, add a real $id to Address and add again `cascade={"persist"}.
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person", cascade={"persist"})
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
If Person were the owning side, Address should also be automatically persisted by Doctrine, don't know your model but maybe you should consider changing it.
Let say I have a Company for which I manage Employees, Cars, Contracts, Buildings, Sites, Products, etc. As you can guess, these are quite independant things, so no inheritance is possible.
For each of these elements (i.e. Entities), I want to be able to attach one or several Documents (click on a button, form opens, select one/several Document or upload a new one).
Linking Document to one kind of entity is not a problem, my problem is that there are many kinds of entities. How should I manage that? I have 2 ideas which have their own problems...:
Create a ManyToMany relationship between Document and Employee, another between Document and Car, etc.
Problem: I have to duplicate the Controller code to attach Document, duplicate the forms, etc.
Create a single join table containing the Document's ID, the related entity's ID and the related entity's class name.
Problem: it doesn't look really clean to me, I didn't really dig in this way but I feel I'll have a lot of "entity mapping" problems.
Any suggestion?
[EDIT]
In fact I have to do the same for Event as well: I need to link some Events to some Employees and/or to some Cars, etc. And in my real case, I have more than 10 Entities to be linked to Event and/or Document, which means duplicating more tha 20 times the code if I go with the solution 1!
Assuming you're using Doctrine ORM, i think you're searching for the Mapped Superclasses inheritance.
The docs are better than words :
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
So finally I managed to solve my problem, following #Rpg600 idea about Mapped Superclasses.
This is probably not the best and cleanest solution ever, I'm not really proud of it but it does the job and it is still better than my first ideas.
I create a BaseEntity which is my a mapped superclass (Employee, Car, etc. Entities have to extend this Class):
/**
* BaseEntity
* #ORM\MappedSuperclass
*/
class BaseEntity {
/**
* #ORM\OneToOne(targetEntity="MyProject\MediaBundle\Entity\Folder")
*/
private $folder;
/**
* Set folder
* #param \Webobs\MediaBundle\Entity\Folder $folder
* #return BaseEntity
*/
public function setFolder(\Webobs\MediaBundle\Entity\Folder $folder = null){
$this->folder = $folder;
return $this;
}
/**
* Get folder
* #return \Webobs\MediaBundle\Entity\Folder
*/
public function getFolder(){
return $this->folder;
}
}
As it is not possible to have a Many-to-Many relationship in a superclass, I use a Folder which will contain one or several Document. This is the dirty part of the solution ; the folder table basically contain only one field which is the id...
class Folder
{
private $id;
/**
* Note : Proprietary side
* #ORM\ManyToMany(targetEntity="MyProject\MediaBundle\Entity\Document", inversedBy="folders", cascade={"persist"})
* #ORM\JoinTable(name="document_in_folder")
*/
private $documents;
// setters and getters
Then I create a helper class (which is declared as a service) to manage the link between any Entity and the Document:
class DocumentHelper extends Controller
{
protected $container;
/** ************************************************************************
* Constructor
* #param type $container
**************************************************************************/
public function __construct($container = null)
{
$this->container = $container;
}
/** ************************************************************************
* Attach Document(s) to an $entity according to the information given in the
* form.
* #param Entity $entity
* #param string $redirectRouteName Name of the route for the redirection after successfull atachment
* #param string $redirectParameters Parameters for the redirect route
* #return Response
**************************************************************************/
public function attachToEntity($entity, $redirectRouteName, $redirectParameters)
{
$folder = $entity->getFolder();
if($folder == NULL){
$folder = new Folder();
$entity->setFolder($folder);
}
$form = $this->createForm(new FolderType(), $folder);
// ------------- Request Management ------------------------------------
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request); // Link Request and Form
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($folder);
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl($redirectRouteName, $redirectParameters));
}
}
return $this->render('MyProjectMediaBundle:Folder:addDocument.html.twig', array(
'form' => $form->createView(),
'entity' => $entity,
));
}
Doing that way, I just have to add one small action in each relevant controller, let say EmployeeController.php:
public function addDocumentAction(Employee $employee)
{
$redirectRouteName = 'MyProjectCore_Employee_see';
$redirectParameters = array('employee_id' => $employee->getId());
return $this->get('myprojectmedia.documenthelper')->attachToEntity($employee,$redirectRouteName,$redirectParameters);
}
Same principle for the display, in the helper I have the common function which I call in my already-existing seeAction() and in the TWIG file I import the common "Document list" display.
That's all folks!
I hope this can help :)
Hi i had fully successfully setted my entity onetoMany and ManyToOne i generated setters and getters and in user entity it created this method:
user entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="followeeuser")
*/
protected $followees;
requests entity:
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id", nullable=false)
*/
protected $followeeuser;
And when i using my own custom queries it works good... but i cant figure out how to use this generated function from symfony:
public function addFollowee(\TB\UserBundle\Entity\User $followee)
{
$this->followees[] = $followee;
}
I dont know what to pass there... i tried first get user object based on id of user from twig... worked good but the error occur:
$user->addFollowee($userRepository->find($target_user_id));
Found entity of type TB\UserBundle\Entity\User on association TB\UserBundle\Entity\User#followees, but expecting TB\RequestsBundle\Entity\Requests
Maybe you should think about what you're trying to before coding it. Grab a pen and a sheet of paper. :)
Tell me if I'm wrong, but here is what I think you're trying to do :
One user can have many "followee".
One "followee" can have one user.
So, a OneToMany relation is ok.
Here is how to write it, from the doc :
Requests.php (btw, you should use Request.php)
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="requests")
**/
private $user;
User.php
/**
* #ORM\OneToMany(targetEntity="Requests", mappedBy="user", cascade={"all"})
**/
private $requests;
public function __construct()
{
$this->requests = new \ArrayCollection();
}
Now you can check if you your relation is ok, and update your schema :
php app/console doctrine:schema:validate
php app/console doctrine:schema:update --force
About getters/setters :
Requests.php
public function getUser()
{
return $this->user;
}
public function setUser(User $user) // Please add a Use statement on top of your document
{
$this->user = $user;
return $this;
}
User.php
public function addRequest(Requests $request)
{
$this->requests->add($request);
return $this;
}
public function removeRequest(Requests $request)
{
$this->requests->removeElement($request);
return $this;
}
// Get requests and set requests (you know how to write those ones)
Now, to set a user to a Request, use
$request->setUser($user);
And to add a Request to a user, use
$user->addRequest($request);