Radolino
Radolino

Reputation: 1844

How to use collections in Symfony2 with OneToMany and ManyToOne relationships?

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">&times;</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">&times;</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">&times;</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">&times;</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:

Upvotes: 0

Views: 291

Answers (3)

Radolino
Radolino

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

Cerad
Cerad

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

Flip
Flip

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

Related Questions