Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One-to-Many-to-One with attributes Form with Symfony 3 / Doctrine

Here is the problem :

I have a model with 3 classes

  • person
  • person_job
  • job

A person can have several jobs, any job-person relation can have a "date_start" attribute, "date_end", and "comment". So I built this model with a jointable (person_job) holding these attributes, and making the relationship on 2 manyToOne attributes called person and job (generated with doctrine annotations)

Person attributes looks like :

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

/**
* @var string
* @ORM\Column(name="firstname",type="string",length=255,nullable=true)
*/
private $firstname;
/**
* @var bool
* @ORM\Column(name="active", type="boolean")
*/
private $active;

Job attributes looks like this :

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

person_job looks like this :

/**
* @ORM\ManyToOne(targetEntity="...\Person")
* @ORM\JoinColumn(nullable=false)
*/
private $person;

/**
* @ORM\ManyToOne(targetEntity="...\Job")
* @ORM\JoinColumn(nullable=false)
*/
private $job;

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

/**
* @var \DateTime
* @ORM\Column(name="startdate",type="datetime")
*/
private $datestart;

/**
* @var \DateTime
* @ORM\Column(name="enddate",type="datetime")
*/
private $dateend;

Now I'd like to build a form, for my "person" where I can choose jobs in a list, and add (if needed) date_start, date_end, or comment related to this job. My FormBuilder looks like this for "Person" :

$builder
  ->add('name')
  ->add('firstname')
  ->add('jobs',Job::class,array('label'=>'Job'));

This fails. My Person class has no "jobs" attribute.. So, how can I achieve such things? do I have to add a jobs attribute, with a oneToMany relation on it, declared with "mappedBy"?

These relationships in doctrine still make me confused, I'm new to symfony, I looked on the internet but didn't find a decent solution/example yet...

Thanks for reading/help

like image 318
Julo0sS Avatar asked Feb 06 '23 16:02

Julo0sS


1 Answers

You have come accross one of the hardest problems with Symfony forms. Fortunately, there is some good documentation. Let me summarize the important steps.

You’re right: The entity Person needs to know about its relationship with PersonJob if you want to manipulate that relationship from a Person’s point of view. So you need to add a property:

// src/AppBundle/Entity/Person.php
/**
 * @ORM\OneToMany(targetEntity="PersonJob", mappedBy="person")
 */
private $personJobs;

public function __construct()
{
    $this->personJobs = new \Doctrine\Common\Collections\ArrayCollection();
}

and then you will have in the form type

// src/AppBundle/Form/PersonType.php
$builder
    ->add('name')
    ->add('firstname')
    ->add('personJobs', CollectionType::class, array(
        'entry_type'   => PersonJobType::class,
        'allow_add' => true,
    )
;

Note the type of the personJobs field. Since a person can have many PersonJobs, you need a form type that can handle collections. This is the purpose of the built-in CollectionType (check out its documentation!). You also need the form type PersonJobType, so that CollectionType knows how to build the sub-forms:

class PersonJobType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('comment')
            ->add('datestart', DateTimeType::class)
            ->add('dateend', DateTimeType::class)
            ->add('job') // requires Job::__toString() to be defined!
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\PersonJob'
        ));
    }
}

For debugging purposes, change your controller to

 public function testAction()
 {
    $person = new Person();
    $form = $this->createForm(PersonType::class, $person);
    $form->add('submit', SubmitType::class);

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        print '<pre>';
        var_dump($form->getData());
        die();
    }

    return $this->render('default/index.html.twig', [
        'form' => $form->createView(),
    ]);
}

Now go ahead and copy & paste the Twig and Javascript code from Adding and Removing Items (you have to make minor changes such as replacing form.emails with form.personJobs).

The form

The form will look like

Just the Person form with a “Add another PersonJob” link:

First step

Adding a PersonJob:

Adding a PersonJob

Adding anothing PersonJob:

Adding another PersonJob

The data received

Submit the form and see the output of var_dump:

object(AppBundle\Entity\Person)#247 (5) {
  ["id":"AppBundle\Entity\Person":private]=>
  NULL
  ["name":"AppBundle\Entity\Person":private]=>
  string(12) "Charles Dude"
  ["firstName":"AppBundle\Entity\Person":private]=>
  string(7) "Charles"
  ["active":"AppBundle\Entity\Person":private]=>
  bool(true)
  ["personJobs":"AppBundle\Entity\Person":private]=>
  object(Doctrine\Common\Collections\ArrayCollection)#248 (1) {
    ["elements":"Doctrine\Common\Collections\ArrayCollection":private]=>
    array(2) {
      [0]=>
      object(AppBundle\Entity\PersonJob)#962 (6) {
        ["id":"AppBundle\Entity\PersonJob":private]=>
        NULL
        ["comment":"AppBundle\Entity\PersonJob":private]=>
        string(19) "Something important"
        ["datestart":"AppBundle\Entity\PersonJob":private]=> 
        object(DateTime)#1088 (3) { … }
        ["dateend": …] => …
        ["person":"AppBundle\Entity\PersonJob":private]=>
        NULL
        ["job":"AppBundle\Entity\PersonJob":private]=>
        object(AppBundle\Entity\Job)#1171 (2) {
          ["id":"AppBundle\Entity\Job":private]=>
          int(2)
          ["name":"AppBundle\Entity\Job":private]=>
          string(5) "Job 2"
        }
      }
      [1]=> …
  }
}

Two things remain to be done:

  1. Set the person property of the nested PersonJob entities properly to the new (but not yet persisted) Person.

  2. Tell Doctrine about the new PersonJob entities by calling $em->persist(…) on them.

Relevant documentation:

  • CollectionType Field
  • How to Embed a Collection of Forms
like image 76
Lumen Avatar answered Feb 10 '23 06:02

Lumen