Nay Lin Aung
Nay Lin Aung

Reputation: 73

Symfony doctrine many to one relation with embedded form collection

I have 2 entities : Doctor & Timetable where one doctor has many timetables(OneToMany) and many timetables have one doctor(ManyToOne).I want to create many timetables from Doctor Create form Screen. So, i followed the article embedded form collection Everything is working fine except that I am getting the doctors dropdown on timetable create session which means I can only create timetable for existing doctors. I know that the doctors dropdown came from ManyToOne relationships. But for my case, I need to create timetable for current doctor(the one that gonna create on the screen) without choosing the dropdown of existing doctors. To tell the logic what I want with pseudo codes:

//Doctor Create Action 
        public function createAction(Request $request)
    {
        $entity = new Doctor();
        $form = $this->createCreateForm($entity);
        $form->handleRequest($request);

    if ($form->isValid()) {

      // Save(Persist) Doctor   
      // Grab Newly Created Doctor ID
      // Create Timetables  with that Doctor ID
      // done   
    }

    return array(
        'entity' => $entity,
        'form'   => $form->createView(),
    );
}

I have read all similar questions but no solution for me. Please anyone help me with some example that symfony beginner can understand.Thank you.

Below is the table structures & related codes :

Doctor Entity Table

id           INT PRIMARY KEY AUTO_INCREMENT
name     VARCHAR(55) NOT NULL

Timetable Entity Table
id                   INT PRIMARY KEY AUTO_INCREMENT
doctor_id       INT (foreign key to doctor)
clinic_id         INT (foreign key to clinic)
_time             VARCHAR(255) NOT NULL

Doctor Entity Class

<?php

namespace Nay\MMClinicsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * Doctor
 *
 * @ORM\Table(name="doctors")
 * @ORM\Entity(repositoryClass="Nay\MMClinicsBundle\Entity\DoctorRepository")
 */

class Doctor
{



/**
 * @var ArrayCollection
 *
 * @ORM\OneToMany(targetEntity="Timetable", mappedBy="doctor", cascade={"persist"})
 */
public $timetables;

/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="doctor_name", type="string", length=255)
 */
private $doctorName;



public function __construct(){
    $this->timetables = new ArrayCollection();

}


/**
*get timetable of this doctor
* @return arraycollection
*/
public function getTimetables(){
    return $this->timetables;
}

public function addTimetable(Timetable $t)
{
    $this->timetables->add($t);
}

public function removeTimetable(Timetable $t)
{
    $this->timetables->removeElement($t);
}


        // **setters & getters for each property here** 


}

Timetable Entity Class

<?php

namespace Nay\MMClinicsBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Timetable
 *
 * @ORM\Table(name="timetables")
 * @ORM\Entity(repositoryClass="Nay\MMClinicsBundle\Entity\TimetableRepository")
 */
class Timetable
{

/**
 * @var Clinic
 *
 * @ORM\ManyToOne(targetEntity="Clinic", inversedBy="timetables")
 * @ORM\JoinColumn(name="clinic_id", referencedColumnName="id")
 */
public $clinic;

/**
 * @var Doctor
 *
 * @ORM\ManyToOne(targetEntity="Doctor", inversedBy="timetables")
 * @ORM\JoinColumn(name="doctor_id", referencedColumnName="id")
 */
public $doctor;


/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var integer
 *
 * @ORM\Column(name="clinic_id", type="integer")
 */
private $clinicId;

/**
 * @var integer
 *
 * @ORM\Column(name="doctor_id", type="integer")
 */
private $doctorId;

/**
 * @var string
 *
 * @ORM\Column(name="_time", type="string", length=1024)
 */
private $time;


  //**setters & getters for each property here** 

}

Doctor Form Type

    /**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('doctorName')
        ->add('timetables', 'collection', array('type' => new TimetableType(),'allow_add' => true ,'by_reference' => false, 'allow_delete' => true ));
    ;
}

Timetable Form Type

<?php

namespace Nay\MMClinicsBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class TimetableType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('clinic')
            ->add('doctor')
            ->add('time','text',array('attr'=>array("data-role"=>"tagsinput")))
        ;
    }

/**
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Nay\MMClinicsBundle\Entity\Timetable'
    ));
}

/**
 * @return string
 */
public function getName()
{
    return 'nay_mmclinicsbundle_timetable';
    }
}

Upvotes: 1

Views: 3476

Answers (2)

Oleg Abrazhaev
Oleg Abrazhaev

Reputation: 2859

http://www.doctrine-project.org/jira/browse/DDC-3480

http://www.doctrine-project.org/jira/browse/DDC-3556

Mention that only basic mapping is supported by embeddables

So, ManyToOne with Embedded still not supported by Doctrine 2, it's a pity.

Upvotes: 2

herr
herr

Reputation: 847

Even I am not sure this way is recommended. but I had solved it in following way. First remove the following line from timetable form type:

->add('doctor')

In your controller createAction() add this code after your doctor entity save/flush.

$doctor = $form->getData();
if($doctor->getTimetables())
{
     foreach ($doctor->getTimetables() as $timetable) {
           $timetable->setDoctor($doctor);
           $em->persist($timetable);
           $em->flush();
     }
}

In your controller updateAction() add this code befor your form handleRequest.

$entity = $em->getRepository('YourBundle:Doctor')->find($id);
$originalTimetables = new ArrayCollection();
if($entity->getTimetables())
{
     foreach ($entity->getTimetables() as $timetable) {
          $originalTimetables->add($timetable);
     }
}

In your controller updateAction() add this code after your form handleRequest and before doctor entity flush.

$editForm->handleRequest($request);
if ($editForm->isValid()) {
   foreach ($originalTimetables as $timetable) 
   {
       if (false == $entity->getTimetables()->contains($timetable)) 
       {     
           $timetable->getDoctor()->removeTimetable($timetable);
           $em->remove($timetable);
           $em->flush();
       }
   }
   $em->flush();

In your controller updateAction() add this code after your doctor entity flush.

   $doctor = $editForm->getData();
   if($doctor->getTimetables())
   {
        foreach ($doctor->getTimetables() as $timetable) 
        {
           $timetable->setDoctor($doctor);
           $em->flush();
        }
   }
}

I am not sure and even not tried $em->flush() will required in each loop or will work out side of loop only once it will work. :)

Upvotes: 0

Related Questions