Reputation: 1844
I have a database with Contacts (Contact entity), Contacts' relatives (ContactRelative entity) and various occasions (AnniBirth entity). What I am firstly trying to do is to persist a Contact with its details in the Contact entity. Secondly, I enter all the Contact's relatives (spouse, child etc.) and I wish to have the ability to enter up to 4 occasions (birthday, anniversary etc.). I have been researching in Symfony2 and doctrine's relationships/ associations for a way to implement this and I decided to use "collections".
Everything is clear to me except the many AnniBirths to ContactRelatives, where I have to display a collection of embedded AnniBirth forms and persist them back to the database.
So far, the relationships are:
Requirements:
Contact.php
<?php
// src/********/***Bundle/Entity/Contact.php
namespace ********\***Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="contact")
*/
class Contact
{
/**
* @ORM\OneToMany(targetEntity="********\***Bundle\Entity\AnniBirth", mappedBy="contact", cascade={"all"})
*/
protected $annibirths;
/**
* @ORM\OneToMany(targetEntity="********\***Bundle\Entity\ContactRelative", mappedBy="contact", cascade={"all"})
*/
protected $contactRelatives;
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=100)
*/
protected $firstname;
/**
* @ORM\Column(type="string", length=100)
*/
protected $lastname;
/**
* Constructor
*/
public function __construct()
{
$this->annibirths = new ArrayCollection();
$this->contactRelatives = new ArrayCollection();
}
public function __toString()
{
return $this->getFirstName();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstname
*
* @param string $firstname
* @return Contact
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set lastname
*
* @param string $lastname
* @return Contact
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
/**
* Get lastname
*
* @return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* Add annibirths
*
* @param \********\***Bundle\Entity\AnniBirth $annibirths
* @return Contact
*/
public function addAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirths)
{
$this->annibirths[] = $annibirths;
return $this;
}
/**
* Remove annibirths
*
* @param \********\***Bundle\Entity\AnniBirth $annibirths
*/
public function removeAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirths)
{
$this->annibirths->removeElement($annibirths);
}
/**
* Get annibirths
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getAnnibirths()
{
return $this->annibirths;
}
/**
* Get contactRelatives
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getContactRelatives()
{
return $this->contactRelatives;
}
/**
* Add contactRelatives
*
* @param \********\***Bundle\Entity\ContactRelative $contactRelatives
* @return Contact
*/
public function addContactRelative(\********\***Bundle\Entity\ContactRelative $contactRelatives)
{
$this->contactRelatives[] = $contactRelatives;
return $this;
}
/**
* Set contactRelatives
*
* @param \********\***Bundle\Entity\ContactRelative $contactRelative
* @return Contact
*/
public function setContactRelative(\********\***Bundle\Entity\ContactRelative $contactRelative = null) {
$this->contactRelative = $contactRelative;
return $this;
}
/**
* Remove contactRelatives
*
* @param \********\***Bundle\Entity\ContactRelative $contactRelatives
*/
public function removeContactRelative(\********\***Bundle\Entity\ContactRelative $contactRelatives)
{
$this->contactRelatives->removeElement($contactRelatives);
}
}
AnniBirth.php
<?php
// src/********/***Bundle/Entity/AnniBirth.php
namespace ********\***Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="annibirth")
*/
class AnniBirth
{
/**
* @ORM\ManyToOne(targetEntity="********\***Bundle\Entity\Contact", inversedBy="annibirths")
* @ORM\JoinColumn(name="contact_id", referencedColumnName="id")
*/
protected $contact;
/**
* @ORM\ManyToOne(targetEntity="********\***Bundle\Entity\ContactRelative", inversedBy="annibirths")
* @ORM\JoinColumn(name="contactRelative_id", referencedColumnName="id")
*/
protected $contactRelative;
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="date")
*/
protected $celebrationDate;
/**
* @ORM\Column(type="boolean", nullable=false)
*/
protected $repeating;
/**
* @ORM\Column(type="string", length=30)
*/
protected $type;
public function __toString()
{
return $this->getType();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set celebrationDate
*
* @param \DateTime $celebrationDate
* @return AnniBirth
*/
public function setCelebrationDate($celebrationDate)
{
$this->celebrationDate = $celebrationDate;
return $this;
}
/**
* Get celebrationDate
*
* @return \DateTime
*/
public function getCelebrationDate()
{
return $this->celebrationDate;
}
/**
* Set contact
*
* @param \********\***Bundle\Entity\Contact $contact
* @return AnniBirth
*/
public function setContact(\********\***Bundle\Entity\Contact $contact = null)
{
$this->contact = $contact;
return $this;
}
/**
* Get contact
*
* @return \********\***Bundle\Entity\Contact
*/
public function getContact()
{
return $this->contact;
}
/**
* Set repeating
*
* @param boolean $repeating
* @return AnniBirth
*/
public function setRepeating($repeating)
{
$this->repeating = $repeating;
return $this;
}
/**
* Get repeating
*
* @return boolean
*/
public function getRepeating()
{
return $this->repeating;
}
/**
* Set type
*
* @param string $type
* @return AnniBirth
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set ContactRelative
*
* @param \********\***Bundle\Entity\ContactRelative $contactRelative
* @return AnniBirth
*/
public function setContactRelative(\********\***Bundle\Entity\ContactRelative $contactRelative = null)
{
$this->contactRelative = $contactRelative;
return $this;
}
/**
* Get contactRelative
*
* @return \********\***Bundle\Entity\ContactRelative
*/
public function getContactRelative()
{
return $this->contactRelative;
}
}
ContactRelative.php
<?php
// src/********/***Bundle/Entity/ContactRelative.php
namespace ********\***Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="contactRelative")
*/
class ContactRelative
{
/**
* @ORM\ManyToOne(targetEntity="********\***Bundle\Entity\Contact", inversedBy="contactRelatives", cascade={"all"})
* @ORM\JoinColumn(name="contact_id", referencedColumnName="id")
*/
protected $contact;
/**
* @ORM\OneToMany(targetEntity="********\***Bundle\Entity\AnniBirth", mappedBy="contactRelative", cascade={"all"})
*/
protected $annibirths;
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=100)
*/
protected $firstname;
/**
* @ORM\Column(type="datetime")
*/
protected $created_at;
/**
* @ORM\Column(type="datetime")
*/
protected $modified_at;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $avatar;
/**
* @ORM\Column(type="string", length=1, nullable=true)
*/
protected $gender;
/**
* @ORM\Column(type="string", length=20, nullable=false)
*/
protected $relation;
/**
* Constructor
*/
public function __construct()
{
$this->annibirths = new ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstname
*
* @param string $firstname
* @return ContactRelative
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set created_at
*
* @param \DateTime $createdAt
* @return ContactRelative
*/
public function setCreatedAt($createdAt)
{
if(!$this->getCreatedAt())
{
$this->created_at = new \DateTime();
}
}
/**
* Get created_at
*
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set modified_at
*
* @param \DateTime $modifiedAt
* @return ContactRelative
*/
public function setModifiedAt($modifiedAt)
{
$this->modified_at = new \DateTime();
}
/**
* Get modified_at
*
* @return \DateTime
*/
public function getModifiedAt()
{
return $this->modified_at;
}
/**
* Set avatar
*
* @param string $avatar
* @return ContactRelative
*/
public function setAvatar($avatar)
{
$this->avatar = $avatar;
return $this;
}
/**
* Get avatar
*
* @return string
*/
public function getAvatar()
{
return $this->avatar;
}
/**
* Set gender
*
* @param string $gender
* @return ContactRelative
*/
public function setGender($gender)
{
$this->gender = $gender;
return $this;
}
/**
* Get gender
*
* @return string
*/
public function getGender()
{
return $this->gender;
}
/**
* Set contact
*
* @param \********\***Bundle\Entity\Contact $contact
* @return ContactRelative
*/
public function setContact(\********\***Bundle\Entity\Contact $contact = null)
{
$this->contact = $contact;
return $this;
}
/**
* Get contact
*
* @return \********\***Bundle\Entity\Contact
*/
public function getContact()
{
return $this->contact;
}
/**
* Set relation
*
* @param string $relation
* @return ContactRelative
*/
public function setRelation($relation)
{
$this->relation = $relation;
return $this;
}
/**
* Get relation
*
* @return string
*/
public function getRelation()
{
return $this->relation;
}
/**
* Add annibirths
*
* @param \********\***Bundle\Entity\AnniBirth $annibirths
* @return ContactRelative
*/
public function addAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirths)
{
$this->annibirths[] = $annibirths;
return $this;
}
/**
* Remove annibirths
*
* @param \********\***Bundle\Entity\AnniBirth $annibirths
*/
public function removeAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirths)
{
$this->annibirths->removeElement($annibirths);
}
/**
* Get annibirths
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getAnnibirths()
{
return $this->annibirths;
}
}
ContactRelativeType.php
<?php
namespace ********\***Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityRepository;
class ContactRelativeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname')
->add('created_at', 'datetime', array(
'input' => 'datetime',
'widget' => 'single_text',
))
->add('modified_at', 'datetime', array(
'input' => 'datetime',
'widget' => 'single_text',
))
->add('avatar')
//->add('contact')
->add('relation', 'choice', array(
'choices' => array(
'Mother' => 'Mother',
'Father' => 'Father',
'Sister' => 'Sister',
'Brother' => 'Brother',
'Child' => 'Child',
),
'required' => true,
'empty_value' => 'Which is the relation?',
))
->add('gender', 'choice', array(
'choices' => array(
'M' => 'Male',
'F' => 'Female'
),
'required' => true,
'empty_value' => 'Choose the gender',
))
->add('annibirths', 'collection', array(
'label' => 'AnniBirths',
'type' => new AnniBirthType(),
'allow_add' => true,
'by_reference' => false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '********\***Bundle\Entity\ContactRelative'
));
}
public function getName()
{
return '********_***Bundle_contactrelativetype';
}
}
ContactController.php (new/create classes)
/**
* Creates a new ContactRelative entity.
*
*/
public function createAction(Request $request)
{
$contactRelative = new ContactRelative();
//$ab1 = new AnniBirth();
//$contactRelative->annibirth = $ab1;
$form = $this->createForm(new ContactRelativeType(), $contactRelative);
ini_set('display_errors', 1);
if ('POST' === $request->getMethod()) {
$form->bind($request);
if ($form->isValid()) {
foreach ( $contactRelative->getAnnibirths() as $anni )
{
$anni-> setContactRelative($contactRelative);
}
$rela = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($contactRelative);
$em->flush();
return $this->redirect($this->generateUrl('contactrelative_show', array('id' => $contactRelative->getId())));
}
}
return $this->render('***********Bundle:ContactRelative:new.html.twig', array(
'entity' => $contactRelative,
'form' => $form->createView(),
));
}
/**
* Displays a form to create a new ContactRelative entity.
*
*/
public function newAction()
{
$entity = new ContactRelative();
$form = $this->createForm(new ContactRelativeType(), $entity);
return $this->render('***********Bundle:ContactRelative:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
ContactRelative new.twig.html
{% extends '***********Bundle::layout.html.twig' %}
{% block body -%}
<h1>ContactRelative creation</h1>
<form action="{{ path('contactrelative_create') }}" method="post" {{ form_enctype(form) }}>
<!---------------- firstname ------------------>
<fieldset class="control-group">
<label class="control-label" for="inputInfo">Firstname</label>
<div class="controls">
{{ form_widget(form.firstname,{'id' : 'inputFirstname', 'attr': { 'class': 'span5' }}) }}
{% if form_errors(form.firstname) %}
<div class="alert alert-error fade in">
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{{ form_errors(form.firstname) }}</p>
</div>
{% endif %}
</div>
</fieldset>
<!---------------- avatar ------------------>
<fieldset class="control-group">
<label class="control-label" for="inputInfo">Avatar</label>
<div class="controls">
{{ form_widget(form.avatar,{'id' : 'inputAvatar', 'attr': { 'class': 'span5' }}) }}
{% if form_errors(form.avatar) %}
<div class="alert alert-error fade in">
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{{ form_errors(form.avatar) }}</p>
</div>
{% endif %}
</div>
</fieldset>
<!---------------- gender ------------------>
<fieldset class="control-group">
<label class="control-label" for="inputInfo">Gender</label>
<div class="controls">
{{ form_widget(form.gender,{'id' : 'inputGender', 'attr': { 'class': 'span5' }}) }}
{% if form_errors(form.gender) %}
<div class="alert alert-error fade in">
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{{ form_errors(form.gender) }}</p>
</div>
{% endif %}
</div>
</fieldset>
<!---------------- relation ------------------>
<fieldset class="control-group">
<label class="control-label" for="inputInfo">Relation</label>
<div class="controls">
{{ form_widget(form.relation,{'id' : 'inputRelation', 'attr': { 'class': 'span5' }}) }}
{% if form_errors(form.relation) %}
<div class="alert alert-error fade in">
<button type="button" class="close" data-dismiss="alert">×</button>
<p>{{ form_errors(form.relation) }}</p>
</div>
{% endif %}
</div>
</fieldset>
{{ form_widget(form.annibirths.vars.prototype)}}
<hr />
{{ form_row(form._token) }}
<p>
<button type="submit">Create</button>
</p>
</form>
<ul class="record_actions">
<li>
<a href="{{ path('contactrelative') }}">
Back to the list
</a>
</li>
</ul>
{% endblock %}
AnniBirthType.php
namespace *******\***Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityRepository;
class AnniBirthType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('celebrationDate','date', array(
'input' => 'datetime',
'widget' => 'choice',
'years' => range(1900,2013),
))
->add('repeating')
->add('type', 'choice', array(
'choices' => array(
'Anniversary' => 'Anniversary',
'Birthday' => 'Birthday',
'Other' => 'Other',
),
'required' => true,
'empty_value' => 'What is the occasion?',
))
->add('contact','entity', array(
'class' => '*******\***Bundle\Entity\Contact',
'property' => 'lastname',
'query_builder' => function(EntityRepository $er)
{
return $er->createQueryBuilder('u')
->orderby('u.lastname','ASC');
},
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '*******\***Bundle\Entity\AnniBirth'
));
}
public function getName()
{
return '*******_***Bundle_annibirthtype';
}
}
PROBLEMS:
The Contact persists all data successfully in the MySQL db. Same for
ContactRelative and AnniBirth BUT when saving data for last two
(ContactRelative and AnniBirth), the contact_id and
contactrelative_id are not updated or saved in the tables. I know I am making a mistake with collections and persisting data. I tried
to display the contact_id by using
\Doctrine\Common\Util\Debug::dump($contactRelative->getAnniBirths()->contact);
exit();
but all I get is NULL.
I want to be able to display the AnniBirth form for 4 times and I do
not know how to implement this. The only thing I found after days of
searching, If I want to display an embedded form without javascript,
I must use "prototype" directly like that : {{
form_widget(form.annibirths.vars.prototype)}}
but only one instance
of the form is displayed.
Upvotes: 0
Views: 291
Reputation: 1844
Found the solution:
Added in controller:
foreach ( $contactRelative->getAnnibirths() as $anni )
{
$anni-> setContactRelative($contactRelative);
$anni-> setContact($contactRelative->getContact());
}
and in twig template:
{% for occasion in form.annibirths %}
<div>
{{ form_row(occasion.type) }}
{{ form_row(occasion.repeating) }}
{{ form_row(occasion.celebrationDate) }}
</div>
{% endfor %}
Upvotes: 0
Reputation: 48893
Part of the problem is this:
public function addAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirths)
{
$this->annibirths[] = $annibirths;
return $this;
}
You will notice that while this points the contact/relative to the annibirth, the annibirth is not pointing back. That is perhaps why another poster may have recommended doing a loop. But a robust and simple solution it to:
public function addAnnibirth(\********\***Bundle\Entity\AnniBirth $annibirth)
{
$this->annibirths[] = $annibirth;
$annibirth->setContact($this); // or setRelative for the Relative entity.
return $this;
}
That might clear up your persisting issues. Too much code to be sure. But absolutely need the above changes. If you look closely at most examples you will see the same sort of line. Easy to overlook.
And once everything is setup properly, there will be no need to loop before persisting.
Upvotes: 1
Reputation: 4908
$contactRelative->annibirth = $ab1;
This shouldn't be possible because you put annibirth as protected within ContactRelative. That should have thrown an error (if you turned errors on earlier). What you should do instead is $contactRelative->setAnnibirth($ab1);
. But that's for making the php objects attached, the attaching in the database actually occrus in the Annibirth table which holds a contactRelative_id so you will have to update this object: $ab1->setContactRelative($contactRelative);
Upvotes: 0