How association mapping Many-To-One or two using doctrine? - php

I have two entities, Student and Responsible (father/mother or tutors). I have to keep both information in the database, so I have to relate both. I think that the best way would be an association Many-To-One adding a foreign key of each responsible (two maximum) in the Student table. I do not know if this would be possible with this association or whether it would be necessary to make a one Many-To-Many association.
use Doctrine\ORM\Mapping as ORM;
class Student{
....
/**
*#ORM\ManyToOne(targerEntity="myBundle\Entity\Responsible")
*#ORM\JoinColumn(name="responsible_id", referencedColumnName="id")
*/
protected $responsible;
}
I'm a bit new to the topic, I appreciate any help possible.

Change to Many-To-Many
I would change to Many-To-Many This means one Responsible can be responsible for several students (a collection of Student) and a student can have many responsibles (a collection of Responsible):
<?php
use Doctrine\ORM\Mapping as ORM;
class Responsible
{
/**
*#ORM\ManyToMany(targetEntity="myBundle\Entity\Student", mappedBy="responsibles")
*/
private $students;
/**
* It is important to initialize your $students collection
*/
public function __construct(){
$students = new ArrayCollection
}
// ALL STUDENT SETTERS + GETTERS
/**
* Get students
*
* #return Collection
*/
public function getStudents()
{
return $this->students;
}
/**
* Add student.
*
* #param Student $student
* #return self
*/
public function addStudent(Student $student)
{
$this->students[] = $student;
return $this;
}
/**
* Add students.
*
* #param Collection $students
* #return self
*/
public function addStudents(Collection $students)
{
foreach ($students as $student) {
$this->addStudent($student);
}
return $this;
}
/**
* Remove student.
*
* #param Student $student
*/
public function removeStudent(Student $student)
{
$this->students->removeElement($student);
}
/**
* Remove students.
*
* #param Collection $students
* #return self
*/
public function removeStudents(Collection $students)
{
foreach ($students as $student) {
$this->removeStudent($student);
}
return $this;
}
}
In your Student:
<?php
use Doctrine\ORM\Mapping as ORM;
class Student
{
/**
*#ORM\ManyToMany(targetEntity="myBundle\Entity\Responsible", mappedBy="student")
* #ORM\JoinTable(name="student_responisble",
* joinColumns={#ORM\JoinColumn(name="student_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="responsible_id", referencedColumnName="id")}
* )
*/
private $responsibles;
/**
* It is important to initialize your $responsibles collection
*/
public function __construct(){
$responsibles = new ArrayCollection
}
// ALL RESPONSIBLE SETTERS + GETTERS
/**
* Get responsibles
*
* #return Collection
*/
public function getResponsibles()
{
return $this->responsibles;
}
/**
* Add responsible.
*
* #param Responsible $responsible
* #return self
*/
public function addResponsible(Responsible $responsible)
{
$this->responsibles[] = $responsible;
return $this;
}
/**
* Add responsibles.
*
* #param Collection $responsibles
* #return self
*/
public function addResponsibles(Collection $responsibles)
{
foreach ($responsibles as $responsible) {
$this->addResponsible($responsible);
}
return $this;
}
/**
* Remove responsible.
*
* #param Responsible $responsible
*/
public function removeResponsible(Responsible $responsible)
{
$this->responsibles->removeElement($responsible);
}
/**
* Remove responsibles.
*
* #param Collection $responsibles
* #return self
*/
public function removeResponsibles(Collection $responsibles)
{
foreach ($responsibles as $responsible) {
$this->removeResponsible($responsible);
}
return $this;
}
}
Alternative solution
If you really want to restrict the amount of responsibles for one student then you could also add the two responsible persons directly to student as $firstResponsible and $secondResponsible and add a custom getResponsibles method so you can get them all at once in an array. You can later also add a $thirdResponsible if necessary of course.
<?php
use Doctrine\ORM\Mapping as ORM;
class Student
{
/**
* #ORM\ManyToOne(targerEntity="myBundle\Entity\Responsible")
* #ORM\JoinColumn(name="first_responsible_id", referencedColumnName="id")
*/
private $firstResponsible;
/**
* #ORM\ManyToOne(targerEntity="myBundle\Entity\Responsible")
* #ORM\JoinColumn(name="second_responsible_id", referencedColumnName="id")
*/
private $secondResponsible;
/**
* Set first responsible
*
* #param Responsible $responsible
* #return self
*/
public function setFirstResponsible(Responsible $responsible)
{
$this->firstResponsible = $responsible;
return $this;
}
/**
* Get first responsible
*
* #return Responsible
*/
public function getFirstResponsible()
{
return $this->firstResponsible;
}
/**
* Set second responsible
*
* #param Responsible $responsible
* #return self
*/
public function setSecondResponsible(Responsible $responsible)
{
$this->secondResponsible = $responsible;
return $this;
}
/**
* Get second responsible
*
* #return Responsible
*/
public function getSecondResponsible()
{
return $this->secondResponsible;
}
/**
* Get student responsibles
*
* #return array
*/
public function getResponsibles()
{
$responsibles = array();
if(isset($this->firstResponsible)){
$responsibles[] = $this->firstResponsible;
}
if(isset($this->secondResponsible)){
$responsibles[] = $this->secondResponsible;
}
return $responsibles;
}
}

Well, a student can have more than one Responsible (tutor, father or mother) and it's presumably possible that a tutor could be mentoring more than one student, or a parent might have more than one child who is a student.
So a many to many relationship would work best here. Even if you have a limit of two Responsible entities, there's still a many to many requirement based on the information you've given.

Of course it's possible as it's not complicated case.
You can use ManyToMany association if one Responsible can have more than one Student else it's enough to use bidirectional ManyToOne association in your Student entity:
use Doctrine\ORM\Mapping as ORM;
class Student
{
/**
*#ORM\ManyToOne(targetEntity="myBundle\Entity\Responsible", inversedBy="student")
*/
private $responsible;
}
and your Responsible entity should be:
use Doctrine\ORM\Mapping as ORM;
class Responsible
{
/**
*#ORM\OneToMany(targetEntity="myBundle\Entity\Responsible", mappedBy="responsible")
*/
private $student;
}
JoinColumn is not necessary if you want to use standard naming for your fields: field_id.
Also you should use private properties if you do not plan to inherit from your entities.
Please read well written official Symfony documentation and Doctrine documentation for more information about the topic.

Related

Embedded forms relations doctrine

in my symfony app, i'm using embedded forms. In my case, an object "CompetenceGroupe" can have multiple objects "CompetenceItem", but an object "CompetenceItem" belongs to only one object "CompetenceGroupe", so the relation is manyToOne.
The form work perfectly, and I have two tables (one for each entity), and it's well saved in the database.
But when I select an CompetenceGroupe object with doctrine in my controller, I have all informations of the object, and he's got an empty "competenceItems" property, so I can't retrieve the childs object (CompetenceItem).
My "CompetenceGroupe" entity :
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="competences_groupes")
*/
class CompetenceGroupe
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id_competence_groupe;
/**
* #var User $user
*
* #ORM\ManyToOne(targetEntity="User", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_user", referencedColumnName="id_user", nullable=false)
*/
private $user;
/**
* #ORM\Column(type="string", length=60, nullable=true)
*/
protected $titre;
protected $competence_items;
public function __construct()
{
$this->competence_items = new ArrayCollection();
}
public function getCompetenceItems()
{
return $this->competence_items;
}
/**
* Get idCompetenceGroupe
*
* #return integer
*/
public function getIdCompetenceGroupe()
{
return $this->id_competence_groupe;
}
/**
* Set titre
*
* #param string $titre
*
* #return CompetenceGroupe
*/
public function setTitre($titre)
{
$this->titre = $titre;
return $this;
}
/**
* Get titre
*
* #return string
*/
public function getTitre()
{
return $this->titre;
}
/**
* Set user
*
* #param \AppBundle\Entity\User $user
*
* #return CompetenceGroupe
*/
public function setUser(\AppBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AppBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
public function addItem(CompetenceItem $item)
{
$this->competence_items->add($item);
}
public function removeItem(CompetenceItem $item)
{
// ...
}
/**
* Set competenceItems
*
* #param \AppBundle\Entity\CompetenceItem $competenceItems
*
* #return CompetenceGroupe
*/
public function setCompetenceItems(\AppBundle\Entity\CompetenceItem $competenceItems = null)
{
$this->competence_items = $competenceItems;
return $this;
}
}
And my "CompetenceItem" entity :
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="competences_items")
*/
class CompetenceItem
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id_competence_item;
/**
* #ORM\Column(type="string", length=60, nullable=false)
*/
protected $libelle;
/**
* #var CompetenceNiveau $niveau
*
* #ORM\ManyToOne(targetEntity="CompetenceNiveau", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_competence_niveau", referencedColumnName="id_competence_niveau", nullable=true)
*/
private $niveau;
/**
* #var CompetenceGroupe $competence_groupe
*
* #ORM\ManyToOne(targetEntity="CompetenceGroupe", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="id_competence_groupe", referencedColumnName="id_competence_groupe", nullable=false)
*/
private $competence_groupe;
/**
* Get idCompetenceItem
*
* #return integer
*/
public function getIdCompetenceItem()
{
return $this->id_competence_item;
}
/**
* Set libelle
*
* #param string $libelle
*
* #return CompetenceItem
*/
public function setLibelle($libelle)
{
$this->libelle = $libelle;
return $this;
}
/**
* Get libelle
*
* #return string
*/
public function getLibelle()
{
return $this->libelle;
}
/**
* Set niveau
*
* #param \AppBundle\Entity\CompetenceNiveau $niveau
*
* #return CompetenceItem
*/
public function setNiveau(\AppBundle\Entity\CompetenceNiveau $niveau = null)
{
$this->niveau = $niveau;
return $this;
}
/**
* Get niveau
*
* #return \AppBundle\Entity\CompetenceNiveau
*/
public function getNiveau()
{
return $this->niveau;
}
/**
* Set competenceGroupe
*
* #param \AppBundle\Entity\CompetenceGroupe $competenceGroupe
*
* #return CompetenceItem
*/
public function setCompetenceGroupe(\AppBundle\Entity\CompetenceGroupe $competenceGroupe)
{
$this->competence_groupe = $competenceGroupe;
return $this;
}
/**
* Get competenceGroupe
*
* #return \AppBundle\Entity\CompetenceGroupe
*/
public function getCompetenceGroupe()
{
return $this->competence_groupe;
}
}
I think I have a missing annotation of the "competence_items" property in the CompetenceGroupe entity, but i'm really not sure ...
Thanks for your help !
A good practice may be to have a competence form, which would be call inside your competence group form
You may add a CollectionType as parrent and include query to search which competence already exist
There are some good example with post form type in symfony demo blog
Or you can use form events (onSubmit, preSubmit, etc...) to charge your entity with your required competence. This example show a message form which allow to choose friend from preset data, this is a good example.
You have tow choice , even to create a Many-To-One, Unidirectional , in this case , you need clean some code , take a look:
In CompetenceGroupe class :
class CompetenceGroupe
{
/**
* Many competence have One Group.
* #ManyToOne(targetEntity="CompetenceItem")
* #JoinColumn(name="id_competence_item", referencedColumnName="id_competence_item")
*/
protected $competence_items;
public function __construct()
{
// $this->competence_items = new ArrayCollection();
//delete that line
}
In CompetenceItem class :
class CompetenceItem
{
You need to delete private $competence_groupe; attribute with his annotation :
By this way, when you dump a CompetenceGroupe object you gonna find the competence items.
Also, you can do it with One-To-Many, Bidirectional ,if you want to get the data from the inverse side and from the owning side .
EDIT: If one competenceGroupe can have many competenceItems, then that is a OneToMany relationship; this is the inverse side of the relationship as defined by doctrine, but that is ok. Your question asked how to pull a competenceGroupe and retrieve all related competenceItems. You can do this by making the competenceItems an ArrayCollection in your CompetenceGroupe entity, just as you have done. You do have to define that further in the annotation, see (updated) code below.
For an ArrayCollection, you can remove your method setCompetenceItems and instead define a method addCompetenceItem in your CompetenceGroupe entity.
class CompetenceGroupe
{
/**
* #ORM\OneToMany(targetEntity="CompetenceItem", mappedBy="competence_groupe")
*/
protected $competenceItems;
public function __construct()
{
$this->competenceItems= new ArrayCollection();
}
/**
* Add competenceItem
*
* #param CompetenceItem $competenceItem
* #return CompetenceGroupe
*/
public function addCompetenceItem(CompetenceItem $competenceItem)
{
$this->competence_items->add($competenceItem);
return $this;
}
}
You'll also need to define the owning side to make all this work.

Doctrine ManyToMany Association Entity: why does removeXXX() not delete underlying database record?

I have a situation where I need to add columns to a many-to-many join table, so I'm trying to follow the recommended practice of having the join table represented by an entity with ManyToOne relationships with each of the other two entities.
In this case, we have a court interpreter management system where there's an entity called Event, another called Interpreter. The InterpreterAssignment entity is one-to-many with both of these, but it also needs two metadata columns: a created datetime, and the Application\Entity\User who created it (I leave out the latter for simplicity's sake).
So, this works just fine:
$interpreter = $entityManager->getRepository('Application\Entity\Interpreter')
->findOneBy(['lastname'=>'Mintz']);
$assignment = new Entity\InterpreterAssignment();
$assignment->setInterpreter($interpreter)->setEvent($event);
$event->addInterpretersAssigned($assignment);
$em->flush();
...and I don't even need to say persist() because of the cascade={"persist","remove"}) on Event#interpretersAssigned.
However, when I try to do the reverse, that is,
use the removeInterpretersAssigned() method that Doctrine wrote for me:
$event = $entityManager->find('Application\Entity\Event',103510);
$assignment = $event->getInterpretersAssigned()[0];
$event->removeInterpretersAssigned($assignment);
$em->flush();
the database is untouched; Doctrine does not delete the row in the join table.
I can work around by saying $entityManager->remove($assignment). But I can't help but think that $event->removeInterpretersAssigned($assignment) is supposed to work.
So, I must be missing something but I can't see what. The Doctrine cli tool says my mappings are OK. Here are the entities, in relevant part:
/* namespace declarations and use statements omitted */
class Event
{
/* other fields and methods omitted */
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="event",cascade={"persist","remove"})
* #var InterpreterAssignment[]
*/
protected $interpretersAssigned;
/* the following created by the Doctrine cli tool */
/**
* Remove interpretersAssigned
*
* #param \Application\Entity\InterpreterAssignment $interpretersAssigned
*/
public function removeInterpretersAssigned(\Application\Entity\InterpreterAssignment $interpretersAssigned)
{
$this->interpretersAssigned->removeElement($interpretersAssigned);
}
/**
* Get interpretersAssigned
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getInterpretersAssigned()
{
return $this->interpretersAssigned;
}
}
class Interpreter
{
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="interpreter")
* #var InterpreterAssignment[]
*/
protected $assignments;
/**
* Remove assignment
*
* #param \Application\Entity\InterpreterAssignment $assignment
*/
public function removeAssignment(\Application\Entity\InterpreterAssignment $assignment)
{
$this->assignments->removeElement($assignment);
}
/**
* Get assignments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAssignments()
{
return $this->assignments;
}
}
and here is the InterpreterAssignment
/**
* #ORM\Entity
* #ORM\Table(name="interp_events", uniqueConstraints={#ORM\UniqueConstraint(name="unique_deft_event",columns={"interp_id","event_id"})})
* #ORM\HasLifeCycleCallbacks
*/
class InterpreterAssignment
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Interpreter",inversedBy="assignments")
* #ORM\JoinColumn(name="interp_id", referencedColumnName="interp_id")
* #var Interpreter
*/
protected $interpreter;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Event",inversedBy="interpretersAssigned")
* #ORM\JoinColumn(name="event_id", referencedColumnName="event_id")
* #var Event
*/
protected $event;
/**
* #ORM\Column(type="datetime",nullable=false)
* #var \DateTime
*/
protected $created;
/**
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->created = new \DateTime();
}
/**
* Set interpreter
*
* #param \Application\Entity\Interpreter $interpreter
*
* #return InterpreterAssignment
*/
public function setInterpreter(\Application\Entity\Interpreter $interpreter)
{
$this->interpreter = $interpreter;
return $this;
}
/**
* Get interpreter
*
* #return \Application\Entity\Interpreter
*/
public function getInterpreter()
{
return $this->interpreter;
}
/**
* Set event
*
* #param \Application\Entity\Event $event
*
* #return InterpreterAssignment
*/
public function setEvent(\Application\Entity\Event $event)
{
$this->event = $event;
return $this;
}
/**
* Get event
*
* #return \Application\Entity\Event
*/
public function getEvent()
{
return $this->event;
}
/* other stuff ommitted */
}
Many thanks.
I think you need to do 2 things:
(optional) You need to call $assignment->setEvent(null) after calling $event->removeInterpretersAssigned($assignment);
Also you may want to use Orphan Removal to remove the entity from the many to many table. and so the entity code should changed to (notice the addition of , orphanRemoval=true to the mapping code):
/**
* #ORM\OneToMany(targetEntity="InterpreterAssignment",mappedBy="event",cascade={"persist","remove"}, orphanRemoval=true)
* #var InterpreterAssignment[]
*/
protected $interpretersAssigned;

Symfony2, Doctrine2 - force update - table already exists on many-to-many relation

After I successfuly created TaskBundle with One-to-Many relation between category and tasks, now I'm trying to create a new TaskBundle with Many-to-Many relation. I get also problem with checking checkbox in this relation, but now it is not a primary problem (maybe after solving this). I deleted all tables, which is TaskBundle using and trying to create a new, but here is problem (description at the bottom).
My Task object:
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200)
* #Assert\NotBlank(
* message = "Task is empty"
* )
* #Assert\Length(
* min = "3",
* minMessage = "Task is too short"
* )
*/
protected $task;
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank()
* #Assert\Type("\DateTime")
*/
protected $dueDate;
/**
* #Assert\True(message = "You have to agree.")
*/
protected $accepted;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="categories")
*/
protected $category;
/**
* Constructor
*/
public function __construct()
{
$this->category = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* #param string $task
* #return Task
*/
public function setTask($task)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return string
*/
public function getTask()
{
return $this->task;
}
/**
* Set dueDate
*
* #param \DateTime $dueDate
* #return Task
*/
public function setDueDate($dueDate)
{
$this->dueDate = $dueDate;
return $this;
}
/**
* Get dueDate
*
* #return \DateTime
*/
public function getDueDate()
{
return $this->dueDate;
}
/**
* Add category
*
* #param \Acme\TaskBundle\Entity\Category $category
* #return Task
*/
public function addCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category[] = $category;
return $this;
}
/**
* Remove category
*
* #param \Acme\TaskBundle\Entity\Category $category
*/
public function removeCategory(\Acme\TaskBundle\Entity\Category $category)
{
$this->category->removeElement($category);
}
/**
* Get category
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCategory()
{
return $this->category;
}
}
and Category object
<?php
namespace Acme\TaskBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=200, unique=true)
* #Assert\NotNull(message="Categories cannot be empty", groups = {"adding"})
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="category")
*/
private $tasks;
public function __toString()
{
return strval($this->name);
}
/**
* Constructor
*/
public function __construct()
{
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
* #return Category
*/
public function addTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks[] = $tasks;
return $this;
}
/**
* Remove tasks
*
* #param \Acme\TaskBundle\Entity\Task $tasks
*/
public function removeTask(\Acme\TaskBundle\Entity\Task $tasks)
{
$this->tasks->removeElement($tasks);
}
/**
* Get tasks
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTasks()
{
return $this->tasks;
}
}
So, after i put doctrine:schema:update --force i'll get error: Table 'symfony.categories' already exists. I've tried to delete all caches, but same problem. Any idea?
There's only problem, if it is as m2m relation.
PS: I was looking for this problem at the Google, but no one answers at this problem. There were only questions, but not correct answers, where the problem is and how to solve it.
Looks like you already have table named "categories" in that database. Remove this line #ORM\JoinTable(name="categories") and try without it.
P.S. "Categories" is really a strange name for join table. You should probably follow some conventions and let doctrine name it. Common names for join tables are category_task or category2task as they are more self-explanatory. Nothing that important, just trying to suggest what I consider good practice.
The thing is that doctrine doesn't understand how your existing table should be used. But you can give him some help.
You have two options :
You don't care about the existing table : simple, you can remove the #ORM\JoinTable(name="categories") annotation, and doctrine will create an other table etc.
You want to keep your existing table, which sounds pretty logical : you have to be more explicit in your annotation by adding #ORM\JoinColumn annotation.
Here is an example:
class
<?php
...
/**
* #ORM\Entity
* #ORM\Table(name="tasks")
*/
class Task
{
...
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="categories",
* joinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="task_id", referencedColumnName="id")})
*/
protected $category;
...
}
and Category object
<?php
...
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
...
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="category")
* #ORM\JoinTable(name="categories",
* joinColumns={#ORM\JoinColumn(name="task_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")})
*/
private $tasks;
...
Doing so, you will be able to keep your table without any doctrine error.
My fix for this, as far as I can tell, was a case-sensitivity issue with table names. Doctrine let me create a Users and a users table but afterwards would die on migrations:diff or migrations:migrate .
I used the -vvv option to get more detail on this error message; it seems that the error happens when Doctrine is loading up its own internal representation of the current database's schema. So if your current database has table names that Doctrine doesn't understand (like two tables that are identical, case-insensitive) then it will blow up in this fashion.
Seems like most of the answers above assume that the error is in your code, but in my case it was in the database.
I got this error with 2 ManyToMany targeting the same entity (User in the exemple below).
To create the table name doctrine use the entity and target entity name.
So in my case it was trying to create two time the table thread_user
To debug this it's easy. Just use the '#ORM\JoinTable' annotation and specify the table name.
Here is a working exemple.
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User")
* #ORM\JoinTable(name="thread_participant")
*/
private $participants;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User")
* #ORM\JoinTable(name="thread_recipient")
*/
private $recipients;
in Symfony4.1 you can force the migration using the migration version
doctrine:migrations:execute <migration version>
ex
for migration version123456.php use
doctrine:migrations:execute 123456
there is another using the table name ,you can search it in your project . Maby be demo,I think it...
sorry for my chinese english !
Try to drop everything inside of your proxy directory.
I fix same issue after check other entities on each bundles, be aware of this.

Doctrine2 and cascade persist - Call to a member function getId() on a non-object

I'm testing cascade persist between Job entity and Category Entity in Symfony2 and Doctrin2.
Here's my Category entity:
<?php
namespace Ibw\JobeetBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Ibw\JobeetBundle\Entity\Affiliate;
use Ibw\JobeetBundle\Entity\Job;
use Ibw\JobeetBundle\Utils\Jobeet;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="Ibw\JobeetBundle\Repository\CategoryRepository")
* #ORM\Table(name="categories")
* #ORM\HasLifecycleCallbacks
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="Job", mappedBy="category", cascade={"persist"})
*/
protected $jobs;
/**
* #ORM\ManyToMany(targetEntity="Affiliate", mappedBy="categories")
*/
protected $affiliates;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
protected $slug;
/**
* Constructor
*/
public function __construct()
{
$this->jobs = new ArrayCollection();
$this->affiliates = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add jobs
*
* #param Job $jobs
* #return Category
*/
public function addJob(Job $jobs)
{
$this->jobs[] = $jobs;
return $this;
}
/**
* Remove jobs
*
* #param Job $jobs
*/
public function removeJob(Job $jobs)
{
$this->jobs->removeElement($jobs);
}
/**
* Get jobs
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getJobs()
{
return $this->jobs;
}
/**
* Add affiliates
*
* #param Affiliate $affiliates
* #return Category
*/
public function addAffiliate(Affiliate $affiliates)
{
$this->affiliates[] = $affiliates;
return $this;
}
/**
* Remove affiliates
*
* #param Affiliate $affiliates
*/
public function removeAffiliate(Affiliate $affiliates)
{
$this->affiliates->removeElement($affiliates);
}
/**
* Get affiliates
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAffiliates()
{
return $this->affiliates;
}
/**
* Set slug
*
* #param string $slug
* #return Category
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* #return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function setSlugValue()
{
$this->setSlug(Jobeet::slugify($this->getName()));
}
}
Here's the cascade persist part in it:
/**
* #ORM\OneToMany(targetEntity="Job", mappedBy="category", cascade={"persist"})
*/
protected $jobs;
Now when i try to test it using this test :
public function testAddJobToCategory()
{
$job = new Job();
$job->setType('flexible-time');
$job->setCompany('Sensio Labs');
$job->setLogo('sensio-labs.gif');
$job->setUrl('http://www.sensiolabs.com/');
$job->setPosition('Web Developer');
$job->setLocation('Paris, France');
$job->setDescription('You\'ve already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available.');
$job->setHowToApply('Send your resume to fabien.potencier [at] sensio.com');
$job->setIsPublic(true);
$job->setIsActivated(true);
$job->setToken('job');
$job->setEmail('job#example.com');
$job->setExpiresAt(new \DateTime('+30 days'));
$category = $this->em->createQueryBuilder('c')
->select('c')
->from('IbwJobeetBundle:Category', 'c')
->where('c.id = 1')
->getQuery()
->getSingleResult();
$category->addJob($job);
$this->em->persist($category);
$this->em->flush();
$jobFromQuery = $this->em->createQueryBuilder('j')
->select('j')
->from('IbwJobeetBundle:Job', 'j')
->where('j.type = :type')
->setParameter('type', 'flexible-time')
->setMaxResults(1)
->setFirstResult(1)
->getQuery()
->getSingleResult();
$this->assertEquals(1, $jobFromQuery->getCategory()->getId());
}
When i run this test, I get this error :
PHP Fatal error: Call to a member function getId() on a non-object
So i think it's in the $jobFromQuery->getCategory()->getId() part. getCategory() being not set or something.
This doctrine doc http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html says:
Doctrine will only check the owning side of an association for changes.
To fully understand this, remember how bidirectional associations are maintained in the object world. There are 2 references on each side of the association and these 2 references both represent the same association but can change independently of one another. Of course, in a correct application the semantics of the bidirectional association are properly maintained by the application developer (that’s his responsibility). Doctrine needs to know which of these two in-memory references is the one that should be persisted and which not. This is what the owning/inverse concept is mainly used for.
Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine’s point of view)
The owning side of a bidirectional association is the side Doctrine “looks at” when determining the state of the association, and consequently whether there is anything to do to update the association in the database
On your code you just do $category->addJob($job); so you're setting the association on the inverse side. As the docs that's ignored so you should at least do $job->setCategory($category) first.

Doctrine 2 Many to Many relation with additional columns in join table

So, I have been playing round with using doctrine for a while now and have it in some basic projects, but i decided to go back and have an in depth look into what it can do.
Ive now decided to switch to symfony 2 as my framework of choice and am looking into what doctrine 2 can do in more depth.
One thing i have been trying to get my head around is the many to many relationship within doctrine. I am starting to build a recipe system and am working on the relation between recipe and ingredients which gave me 3 entities, recipe, recipeIngredient and ingredient. The reason i cannot use a direct many to many relation is because i want to store two additional columns in the join table ( unit and quantity ) for each ingredient.
The problem i am having at the moment is that the entities persist ok, but the recipe_id in the join table is not inserted. I have tried everything i can think off and been through every thread and website looking for an answer . I am sure it is something completely obvious that i am missing. Please help, below is the code i have so far:
<?php
namespace Recipe\RecipeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="recipe")
* #ORM\HasLifecycleCallbacks()
*/
class Recipe{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="RecipeIngredient", mappedBy="recipe", cascade= {"persist"})
*/
protected $ingredients;
/**
* #ORM\Column(type="string")
* #var string $title
*
*/
protected $title;
/**
* Constructor
*/
public function __construct()
{
$this->ingredients = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add ingredients
*
* #param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
* #return Recipe
*/
public function addIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
$ingredients->setRecipe($this);
$this->ingredients[] = $ingredients;
return $this;
}
/**
* Remove ingredients
*
* #param \Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients
*/
public function removeIngredient(\Recipe\RecipeBundle\Entity\RecipeIngredient $ingredients)
{
$this->ingredients->removeElement($ingredients);
}
/**
* Get ingredients
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getIngredients()
{
return $this->ingredients;
}
/**
* Set title
*
* #param string $title
* #return Recipe
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
and recipeIngredient
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
* */
protected $recipe;
/**
* #ORM\ManyToOne(targetEntity="Ingredient", inversedBy="ingredients" , cascade={"persist"})
* */
protected $ingredient;
/**
* #ORM\Column(type="string")
* #var string $quantity
*
*/
protected $quantity;
/**
* #ORM\Column(type="string")
* #var string $unit
*
*/
protected $unit;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set quantity
*
* #param string $quantity
* #return RecipeIngredient
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
return $this;
}
/**
* Get quantity
*
* #return string
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* Set unit
*
* #param string $unit
* #return RecipeIngredient
*/
public function setUnit($unit)
{
$this->unit = $unit;
return $this;
}
/**
* Get unit
*
* #return string
*/
public function getUnit()
{
return $this->unit;
}
/**
* Set recipe
*
* #param \Recipe\RecipeBundle\Entity\Recipe $recipe
* #return RecipeIngredient
*/
public function setRecipe(\Recipe\RecipeBundle\Entity\Recipe $recipe = null)
{
$this->recipe = $recipe;
return $this;
}
/**
* Get recipe
*
* #return \Recipe\RecipeBundle\Entity\Recipe
*/
public function getRecipe()
{
return $this->recipe;
}
/**
* Set ingredient
*
* #param \Recipe\RecipeBundle\Entity\Ingredient $ingredient
* #return RecipeIngredient
*/
public function setIngredient(\Recipe\RecipeBundle\Entity\Ingredient $ingredient = null)
{
$this->ingredient = $ingredient;
return $this;
}
/**
* Get ingredient
*
* #return \Recipe\RecipeBundle\Entity\Ingredient
*/
public function getIngredient()
{
return $this->ingredient;
}
}
Your basic idea is the correct one. If you want to have a ManyToMany relation, but you need to add extra fields in the join table, the way to go is exactly as you have described: using a new entity having 2 ManyToOne relations and some additional fields.
Unfortunately you have not provided your controller code, because most likely your problem is there.
Basically if you do something like:
$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setRecipe($r);
$ri->setQuantity(1);
$em->persist($ri);
$em->flush();
You should always get a correct record in your database table having both recipe_id and ingredient_id filled out correctly.
Checking out your code the following should also work, although I personally think this is more sensitive to mistakes:
$ri = new RecipeIngredient;
$ri->setIngredient($i);
$ri->setQuantity(1);
// here we assume that Recipe->addIngredient also does the setRecipe() for us and
// that the cascade field is set correctly to cascade the persist on $ri
$r->addIngredient($ri);
$em->flush();
For further reading I would suggest the other topics on this subject, such as: Doctrine2: Best way to handle many-to-many with extra columns in reference table
If I understand this model correctly the construction of a recipe and its associated recipeIngredients are concurrent. You might not have an id until you persist and without an id if receipeIngredient->setRecipe() is called the default null will be place in the recipeIngredient->recipe field. This is often handled with cascade: "persist" (not present for the recipe field in your example, but you can handle it explicitly in the controller:
/**
* Creates a new Recipe entity.
*
*/
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new RecipeType());
$form->bind($request);
if ($form->isValid()){
$data = $form->getData();
$recipeId = $data->getId();
$recipeIngredients=$data->getIngredients();
$recipe=$em->getRepository('reciperecipeBundle:Recipe')
->findOneById($RecipeId);
if (null === $Recipe)
{$Recipe=new Recipe();}
foreach ($recipeIngredients->toArray() as $k => $i){
$recipeIngredient=$em->getRepository('reciperecipeBundle:recipeIngredient')
->findOneById($i->getId());
if (null === $recipeIngredient)
{$recipeIngrediente=new RecipeIngredient();}
$recipe->addIngredient($i);
// Next line *might* be handled by cascade: "persist"
$em->persist($recipeIngredient);
}
$em->persist($Recipe);
$em->flush();
return $this->redirect($this->generateUrl('Recipe', array()));
}
return $this->render('reciperecipeBundle:Recipe:new.html.twig'
,array('form' => $form->createView()));
}
Im not really sure if this would be a solution, but its easy yo try it, and probably it will help.
When I create a relationshiop of this kind, I use to write another anotation, the #ORM\JoinColumn, like in this example:
We have an entity A, an entity B, and an class AB wich represents the relationships, and adds some other fields, like in you case.
My relationship would be as follows:
use Doctrine\ORM\Mapping as ORM;
/**
*
*
* #ORM\Table(name="a_rel_b")
* #ORM\Entity
*/
class AB
{
/**
* #var integer
* #ORM\Id
* #ORM\ManyToOne(targetEntity="A", inversedBy="b")
* #ORM\JoinColumn(name="a_id", referencedColumnName="id")
**/
private $a;
/**
* #var integer
* #ORM\Id
* #ORM\ManyToOne(targetEntity="B", inversedBy="a")
* #ORM\JoinColumn(name="b_id", referencedColumnName="id")
**/
private $b;
// ...
name means the name of the field in the relationship table, while referencedColumnName is the name of the id field in the referenced entity table (i.e b_id is a column in a_rel_b that references the column id in the table B )
You can't, because it wouldn't be a relationship anymore [which is, by def, a subset of the cartesian product of the sets of the two original entities].
You need an intermediate entity, with references to both Recipe and Ingredient - call it RecipeElement, RecipeEntry or so, and add the fields you want.
Either, you can add a map to your Recipe, in which you save the attributes for each Ingredient you save, easy to maintain if there are no duplicates.
For further reading, have a look at this popular question.

Categories