I'm training myself on Symfony and struggling with a problem with bidirectional association (very basic) because by dumping my entity in a twig template I verify that data is correct but the association is always null.
My problem is like this one but the solution is not shared.
I read the documentation here and it seems I follow the right steps.
My db contain a Parent table and a Children table related by children.parent_id as foreign key, both table are popolated and I use DOCTRINE:GENERATE:ENTITIES and DOCTRINE:GENERATE:CRUD.
In Parents class I have:
function __construct() {
$this->lastUpd = new \DateTime();
$this->children = new ArrayCollection();
}
/*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Children", mappedBy="parent_id", cascade={"persist"})
*/
private $children;
public function setChildren(ArrayCollection $children) {
return $this->children = $children;
}
public function getChildren() {
return $this->children;
}
In Children class I have:
/**
* #var \AppBundle\Entity\Parents
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Parents", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="parent_id")
* })
*/
private $parent_id;
/**
* Set parent_id
* #param \AppBundle\Entity\Parents $parent_id
* #return Parents
*/
public function setParentID(\AppBundle\Entity\Parents $parent_id= null) {
$this->parent_id = $parent_id;
return $this;
}
/**
* Get parent_id
* #return \AppBundle\Entity\Parents
*/
public function getParentID() {
return $this->parent_id;
}
As additional info looking at Simfony profiler (of parents list page) -> Doctrine -> Entities Mapping I found (with no errors) AppBundle\Entity\Parents and AppBundle\Entity\Type (a working unidirectional OneToMany association).
I am sorry to post a so basic error and I bet the solution is simple but I can't see it.
note: Im assuming that youre not creating an ArrayCollection of children and adding them en'mass.
you dont have any addChild method (which you need to call).
this is easy with an ArrayCollection.
public function addChild(Children $child) {
$this->children->add($child);
}
you could also do with a removeChild as well.
public function removeChild(Children $child) {
$this->children->removeElement($child);
}
then when in your controller.
$child = new Children();
$parent->addChild($child);
then when you persist the parent object, the children will follow due to the cascade persist. I would also add cascade={"remove"} as well, so when you delete the parent, the children will go to.
Related
For the past couple of days I have been trying to create a bidirectionnal ManyToOne-OneToMany relationship in Symfony 3.4
I have two entities. One is Contribution and the other is Source. A Contribution can have several sources. So the relationship should be
Contribution – ManyToOne – Source – OneToMany – Contribution
But I keep getting the following error during $em→flush(); in my controller:
Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in /var/www/html/Edebate/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 605
I do not have any set method related to the Array Collection in my Entity Contribution as I could see in other posts here:
Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given
Symfony-Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given
And the annotations are ok as mentionned here:
Doctrine OneToMany relationship error
Any help would be appreciate ! :)
Here is my Entity Contribution
use Doctrine\Common\Collections\ArrayCollection;
//annotations
abstract class Contribution
{
/**
* #ORM\OneToMany(targetEntity="Shaker\DebateBundle\Entity\Source", mappedBy="parent")
*/
protected $sources;
//Other attributes and methods
public function __construct() {
$this->sources = new ArrayCollection();
}
/**
* Add source
*
* #param \Shaker\DebateBundle\Entity\Source $source
*
* #return Contribution
*/
public function addSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources[] = $source;
return $this;
}
/**
* Remove source
*
* #param \Shaker\DebateBundle\Entity\Source $source
*/
public function removeSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources->removeElement($source);
}
/**
* Get sources
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSources()
{
return $this->sources;
}
}
And this is in my Entity Source:
/**
* #ORM\ManyToOne(targetEntity="Shaker\DebateBundle\Entity\Contribution", inversedBy="sources")
*/
protected $parent;
/**
* Set parent
*
* #param \Shaker\DebateBundle\Entity\Contribution $parent
*
* #return Contribution
*/
public function setParent(\Shaker\DebateBundle\Entity\Contribution $parent = null)
{
$this->parent = $parent;
$parent->addSource($this);
return $this;
}
/**
* Get parent
*
* #return \Shaker\JRQBundle\Entity\Contribution
*/
public function getParent()
{
return $this->parent;
}
And in my Controller, the problem arises with flush:
$formsourcebook->handleRequest($request);
$contributionid=$formsourcebook->get('ContributionId')->getData();
if ($formsourcebook->isValid()) {
$topicargtarget=$this->getContribution($contributionid);
$sourcebook->setUser($user);
$sourcebook->setContribution($topicargtarget);
$em->persist($sourcebook);
$em->flush();
}
I don't know your question very well. However, did you try with this syntax in the Source entity?
private $parent;
// ...
public function __construct() {
$this->parent = new ArrayCollection();
// or new \Doctrine\Common\Collections\ArrayCollection();
}
I think you're forgetting the constructor in the class.
I think you "switched" some logic when working with collections. Here's how I think your "add" method should look like:
public function addSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources[] = $source;
$source->setParent($this);
return $this;
}
And in the other entity:
public function setParent(\Shaker\DebateBundle\Entity\Contribution $parent = null)
{
$this->parent = $parent;
return $this;
}
There are missing variables in your controller snippet, together with the form fields definitions, so you shouldn't work that much after submitting the form. Try to directly map as many fields as you can (even via autoguessing), and even if it looks ugly, but works, but then you can beautify later. Just my two cents with several months of delay.
I'm tying to create one to many relations
A have class
class Interview {
/**
* #OneToMany(targetEntity="Question", mappedBy="question")
*/
private $questions;
public function __construct() {
$this->questions = new ArrayCollection();
}
public function __toString() {
return $this->id;
}
/**
* #return Collection|Question[]
*/
public function getQuestions() {
return $this->questions;
}
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
......
}
another
class Question {
/**
* #ManyToOne(targetEntity="Interview", inversedBy="interview")
* #JoinColumn(name="interview_id", referencedColumnName="id")
*/
private $interview;
public function getInterview() {
return $this->interview;
}
public function setInterview(Interview $interview) {
$this->interview = $interview;
return $this;
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
private $interview_id;
......
}
and Controller for all this
if ($form->isSubmitted() && $form->isValid()) {
$interview = new Interview();
$question = new Question();
$em->persist($interview);
$question->setInterview($interview);
$question->setTitle($request->get('title'));
$em->persist($question);
$em->flush();
return $this->redirectToRoute('homepage');
}
i'm receiving an error:
Entity of type AppBundle\Entity\Question is missing an assigned ID for
field 'interview_id'. The identifier generation strategy for this
entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
Don't understand what the problem and how to fix it.
To enforce loading objects from the database again instead of serving them from the identity map. You can call $em->clear(); after you did $em->persist($interview);, i.e.
$interview = new Interview();
$em->persist($interview);
$em->clear();
It seems like your project config have an error in doctrine mapped part.
If you want automatically generated identifiers instead you need to
adjust the metadata mapping accordingly.
Try to see full doctrine config and do some manipulation with
auto_mapping: false
to true as example or something else...
Also go this , maybe it will be useful.
I am sure, its too late to answer but maybe someone else will get this error :-D
You get this error when your linked entity (here, the Interview entity) is null.
Of course, you have already instantiate a new instance of Interview.But, as this entity contains only one field (id), before this entity is persited, its id is equal to NULL. As there is no other field, so doctrine think that this entity is NULL. You can solve it by calling flush() before linking this entity to another entity
Hiho,
what i want to achieve: i have Projects, and within Project-edit, there is a form where i can drop images on (with dropzone.js) and those images are saved and assigned to the given Project.
Image upload works, the Image entities are being saved to the images table and they have the right project_id. But if i access the Project Enity, "images" is "null" in the project array. not an Collection of Image Entites.
It looks like a simple private Variable with no default value.
I guess my OneToMany and ManyToOne associations don't seem to work.
Some Code:
Project.php
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="project", cascade={"persist, remove"})
*/
private $images;
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getImages()
{
return $this->images;
}
public function addImage(Image $image)
{
$this->images[] = $image;
}
public function removeImage(Image $image) {
$this->images->removeElement($image);
}
Image.php
/**
* #var \AppBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Project", inversedBy="images")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
* })
*/
private $project;
/**
* Set project
*
* #param \AppBundle\Entity\Project $project
*
* #return Image
*/
public function setProject(\AppBundle\Entity\Project $project = null)
{
$this->project = $project;
return $this;
}
/**
* Get project
*
* #return \AppBundle\Entity\Project
*/
public function getProject()
{
return $this->project;
}
Everything is saved to DB
but images is "null" (not an ArrayCollection :( )
Perhaps, there is something missing. But i dont see it somehow (although its soooo basic stuff)
Cheers
Adrian
Do the following in Project.php
In the constructor, add the following:
public function __construct()
{
// ...
$this->images = new ArrayCollection();
}
*Change addImage function to :
public function addImage(Image $image)
{
if(!$this-images->contains($image))
$this-images->add(image);
}
And then in Image.php, change setProject to:
public function setProject(\AppBundle\Entity\Project $project = null)
{
$this->project = $project;
if($project != null)
$project->addImage($this);
return $this;
}
Persist a few images and check if "Images" is not null.
I agree with Medard about the constructor and you also can try to set the fetch parameter to lazy on the oneToMany annotation.
Maybe a miss with the paramConverter
Oh Boy...
as i generated the Entities from DB (with doctrine:generate) there were Mapping files in the middle of the generating process (see http://symfony.com/doc/current/doctrine/reverse_engineering.html)
as soon as i deleted the src/AppBundle/Resources/config/doctrine folder (with orm.config.xml files in it) the Images were showing as persistent ArrayCollection.
but still the results where empty.
so i had to additionally put fetch="EAGER" into the OneToMany Mapping, as Lazyloading didnt seem to work properly (while dumping in twig, the result project.images was not initialized)
So thanks alot for your help. in the end, my images are showing up properly.
it would be nice, if symfony would write mapping errors in the dev.log, so i wouldnt have to search for 2 days.
Cya
Adrian
I'm currently working on a language assessment project which enables you to take an exam in the language you want and evaluate your level. I use Symfony2 framework and work with Doctrine2 as well. My issue is the following one:
I have two entities Exam and Question linked by a Many-To-Many relation (Exam being the owner). Each exam can be related to several questions, and each question can be related to several exams.
Here is my code:
Exam entity
/**
* Exam
*
* #ORM\Table(name="cids_exam")
* #ORM\Entity(repositoryClass="LA\AdminBundle\Entity\ExamRepository")
*/
class Exam
{
...
/**
* #ORM\ManyToMany(targetEntity="LA\AdminBundle\Entity\Question", cascade={"persist"})
* #ORM\JoinTable(name="cids_exam_question")
*/
private $questions;
...
/**
* Constructor
*/
public function __construct()
{
$this->questions = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add questions
*
* #param \LA\AdminBundle\Entity\Question $questions
* #return Exam
*/
public function addQuestion(\LA\AdminBundle\Entity\Question $questions)
{
$this->questions[] = $questions;
return $this;
}
/**
* Remove questions
*
* #param \LA\AdminBundle\Entity\Question $questions
*/
public function removeQuestion(\LA\AdminBundle\Entity\Question $questions)
{
$this->questions->removeElement($questions);
}
/**
* Get questions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getQuestions()
{
return $this->questions;
}
}
As long as it is a unidirectional relation, there is no 'exams' attribute in my Question class.
Now, what I want to do is getting all the questions related to a specific exam, calling the getQuestions() method, like this:
$questions = $exam->getQuestions();
But this method returns an empty array, even if I have data in my database. If I var_dump the $exam variable, I can see the questions array is empty:
object(LA\AdminBundle\Entity\Exam)[47]
private 'id' => int 5
...
private 'questions' =>
object(Doctrine\ORM\PersistentCollection)[248]
private 'snapshot' =>
array (size=0)
empty
private 'owner' => null
private 'association' => null
private 'em' => null
private 'backRefFieldName' => null
private 'typeClass' => null
private 'isDirty' => boolean false
private 'initialized' => boolean false
private 'coll' =>
object(Doctrine\Common\Collections\ArrayCollection)[249]
private '_elements' =>
array (size=0)
...
I think I could maybe write a findByExam() function in my QuestionRepository, but I don't really know how to implement the joins in this case.
Any help would be great!
To have a findByExam() method in your QuestionRepository do the following:
public function findByExam($exam)
{
$q = $this->createQueryBuilder('q')
->where('q.exam = :exam')
->setParameter('exam', $exam)
->getQuery();
return $q->getResult();
}
You could also create a bi-directional relationship not uni-directional !
Each exam can be related to several questions, and each question can
be related to several exams.
Create a bi-directional relationship by adding this to your Question entity:
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Vendor\YourExamBundle\Entity\ExamInterface;
class Question
{
protected $exams;
public function __construct()
{
$this->exams = new ArrayCollection();
}
public function getExams()
{
return $this->exams;
}
public function addExam(ExamInterface $exam)
{
if !($this->exams->contains($exam)) {
$this->exams->add($exam);
}
return $this;
}
public function setExams(Collection $exams)
{
$this->exams = $exams;
return $this;
}
// ...
Afterwards you can use...
$question->getExams()
... in your controller.
To automatically join your related entities doctrine's fetch option can be used with:
LAZY ( loads the relations when accessed )
EAGER ( auto-joins the relations )
EXTRA_LAZY ( manual fetching )
example:
/**
* #ManyToMany(targetEntity="Question",inversedBy="exams", cascade={"all"}, fetch="EAGER")
*/
Though eager loading has a downside in terms of performance it might be an option for you.
Doctrine Fetch with EAGER
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
Read more about it in the Doctrine Documentation.
Another option you should check when working with relations is the cascade option.
See the Doctrine - Working with Associations chapter of the documentation.
Tip:
You should create interfaces for exams and questions and use them instead of the original entity in your set and add methods to allow easier extending.
Bi-Directional Relations using Doctrine2 ORM with association table exam_questions
exam_id question_id
<?php
class Exams
....OTHER PROPERTIES...
/**
* Owning Side
*
* #ManyToMany(targetEntity="Questions", inversedBy="exams")
* #JoinTable(name="exam_questions",
* joinColumns={#JoinColumn(name="exam_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="question_id", referencedColumnName="id")}
* )
*/
private $questions;
..OTHER CODES..
}
class Questions{
..OTHER CODES..
/**
* Inverse Side
*
* #ManyToMany(targetEntity="Exams", mappedBy="questions")
*/
private $exams;
..OTHER CODES..
}
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref-manytomany
I'm quite new to Doctrine2 which I'm currently using in a Symfony2 project.
I'm trying to persist entities with a self-referencing foreign key such as the category example of the Doctrine documentation (http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html#one-to-many-self-referencing). There is probably a very easy solution to this but I just couldn't find it anywhere on the web.
For some reason, the value for the parent_id is not automatically stored by Doctrine (it's null), although I can access the id value of the object I am assigning as a parent.
Some code will probably be straightforward. Here's the relevant part :
The entity definition :
class Area
{
// id, name, type, etc...
/**
* #ORM\ManyToOne(targetEntity="Area", inversedBy="sub_areas", cascade={"persist"})
* #ORM\JoinColumn(name="parent_area_id", referencedColumnName="id")
*/
private $parent_area;
/**
* #ORM\OneToMany(targetEntity="Area", mappedBy="parent_area", cascade={"persist"})
*/
private $sub_areas;
/**
* Set parent area
*
* #param obj $area
*/
public function setParentArea(Area $area)
{
$this->aera = $area;
}
// Other getters and setters, etc.
}
In the action :
$results = array();
foreach($area_types as $key => $type) {
$area = new Area();
$area->setType($type);
$area->setName($location->getAddressComponent($type));
if(isset($parent_area)) {
$area->setParentArea($parent_area);
}
$this->em->persist($area);
$parent_area = $area;
$results[] = $area->getId();
}
$this->em->flush();
The results array will output the assigned ids in Twig. I also tried using :
$area->setParentAreaId($parent_area->getId());
Could anyone explain how Doctrine manages the database persistence sequence of self-referenced objects ?