I have profiles and studies. One person can finish many studies. The form renders correctly. There is a button "Add new study" and with jQuery I add another subform based on data-prototype and this works well. When I submit such a form with new subforms I get an database error
Integrity constraint violation: 1048 Column 'profile_id' cannot be null
I understand this error but I don't know how to get rid of it. I know I can update collection of studies after binding in controller but I hope there is a way to configure it properly in annotations. When I only update entities everything works fine.
The code is:
class Profile {
/**
* @var integer $profileId
*
* @ORM\Column(name="profile_id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $profileId;
...
/**
*
* @var Study
* @ORM\OneToMany(targetEntity="Study", mappedBy="profile", cascade={"persist", "remove"})
*/
private $study;
public function __construct()
{
$this->study = new \Doctrine\Common\Collections\ArrayCollection();
}
...
}
and
class Study {
/**
* @var integer $studyId
*
* @ORM\Column(name="study_id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $studyId;
...
/**
* @var Profile
*
* @ORM\ManyToOne(targetEntity="Profile")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="profile_id", referencedColumnName="profile_id")
* })
*/
private $profile;
...
}
with s(g)etters. Underlaying database structure is
CREATE TABLE IF NOT EXISTS `profile` (
`profile_id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`profile_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `study` (
`study_id` int(11) NOT NULL AUTO_INCREMENT,
`profile_id` int(11) NOT NULL,
PRIMARY KEY (`study_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `study`
ADD CONSTRAINT `study_fk2` FOREIGN KEY (`profile_id`) REFERENCES `profile` (`profile_id`);
Form buidlers are:
class ProfileType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('study', 'collection', array(
'type' => new StudyType(),
'allow_add' => true,
'allow_delete' => true
)
)
}
...
}
and
class StudyType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('city') //example field not included in above structures
}
...
}
The Javascript part
function profileNewStudy() {
var nr = $('[class^=profile_study_][class*=type2]').last().parent();
nr = nr.attr('id').split('_');
nr = nr.pop()*1 + 1;
var proto = $('#profile_study').data('prototype');
proto = proto.replace(/\$\$name\$\$/g, nr);
$('#profile_study').append(proto).find('.profile_study_' + nr + ' input').val('qpa' + nr);
}
and Twig template
<form action="#" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" value="Zapisz"/>
</form>
For testing purposes I removed from database constraint NOT NULL on study.profile_id and then everything was saved except that study.profile_id=null.
edited after @Gremo answer
I did some tests. Unfortunately it didn't help :( I corrected his mistakes with code
class Profile
/**
* @var Study
* @ORM\OneToMany(targetEntity="Study", mappedBy="profile")
*/
private $study;
class Study
/**
* @var Profile
* @ORM\ManyToOne(targetEntity="Profile", inversedBy="study")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE", referencedColumnName="profile_id")
*/
private $profile;
and when I added new Study entity in form and posted it to server I got an error: A new entity was found through the relationship 'Alden\xxxBundle\Entity\Profile#study' that was not configured to cascade persist operations for entity: Alden\xxxBundle\Entity\Study@0000000073eb1a8b00000000198469ff. Explicitly persist the new entity or configure cascading persist operations on the relationship. If you cannot find out which entity causes the problem implement 'Alden\xxxxBundle\Entity\Study#__toString()' to get a clue.
So I added cascade to profile:
/**
* @var Study
* @ORM\OneToMany(targetEntity="Study", mappedBy="profile", cascade={"persist"})
*/
private $study;
and than I got an error like in the begining: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'profile_id' cannot be null.
edited My controller code:
$request = $this->getRequest();
$r = $this->getProfileRepository();
$profile = $id ? $r->find($id) : new \Alden\BonBundle\Entity\Profile();
/* @var $profile \Alden\BonBundle\Entity\Profile */
$form = $this->createForm(new ProfileType(), $profile);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
$vacation = $profile->getVacation();
foreach($vacation as $v) {
$r=5;
}
$em = $this->getEntityManager();
$em->persist($profile);
$em->flush();
//return $this->redirect($this->generateUrl('profile_list'));
}
}
return array(
'profile' => $profile,
'form' => $form->createView()
);
SOLUTION
In Profile class, important parts are cascade, orphanRemoval in annotations and foreach loop in setStudies() (thanks to suggestion @Parhs)
/**
* @var \Doctrine\Common\Collections\ArrayCollection
* @ORM\OneToMany(targetEntity="Study", mappedBy="profile", cascade={"ALL"}, orphanRemoval=true)
*/
private $studies;
public function __construct()
{
$this->studies = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @return Doctrine\Common\Collections\Collection
*/
public function getStudies()
{
return $this->studies;
}
public function setStudies($studies)
{
foreach ($studies as $v)
{
if (is_null($v->getProfile()))
{
$v->setProfile($this);
}
}
$this->studies = $studies;
}
In Study class
/**
* @var Profile
* @ORM\ManyToOne(targetEntity="Profile", inversedBy="studies")
* @ORM\JoinColumn(name="profile_id", nullable=false, onDelete="CASCADE", referencedColumnName="profile_id")
*/
private $profile;
and usual getters and setters.
Your solution did not work for me. In fact I had already set up the relationship and defined the adder, remover, getter and after I saw your solution i added the setter.
However my setter was not called when the form was submitted.
The solution for me was another one.
In the adder method I added a call to set the owner.
And to have this adder called when the form is submitted you need to add to the form collection field the
by_reference
property set tofalse
.Here is the documentation:
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
If i understand you correctly your relation is bidirectional, so you have to specify an owning side and an inverse side. As Doctrine 2 documentation:
So i messed up with your association in my first answer. In your case
Profile
has to be the inverse side while study should be the owning side. But you are working onProfile
, so you need thecascade
annotation on profile to persist new entities:Note that your example is exactly the same as that on Doctrine2 documentation.
You have to set profile to instance. In controller. You havent pasted controller code.. You should iterate over $profile->getStudy collection and set for each item ->setProfile=$profile.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#use-case-1-dynamic-attributes
What is happening there doesnt apply to forms..He actually has a this reference..Hm maybe adding this is a kind of auto fix to avoid iteration, not sure whether it works, i'll try