Home » Php » php – symfony3: create form for entity that has a column with many to one relationship

php – symfony3: create form for entity that has a column with many to one relationship

Posted by: admin July 12, 2020 Leave a comment

Questions:

I have a “Tasks” table that references a “Estados” table with a foreign key from column Tasks.taskestado to Estados.estado.

Here’s the relevant XML mapping for Tasks:

<entity name="AppBundle\Entity\Tasks" table="TASKS" repository-class="AppBundle\Repository\TasksRepository">
<id name="taskid" type="bigint" column="TaskID">
  <generator strategy="IDENTITY"/>
</id>
...
<many-to-one field="taskestado" target-entity="Estados" fetch="LAZY">
  <join-columns>
    <join-column name="TaskEstado" referenced-column-name="Estado"/>
  </join-columns>
</many-to-one>
...

And for Estados:

<entity name="AppBundle\Entity\Estados" table="ESTADOS">
<id name="estado" type="string" column="Estado" length="15">
  <generator strategy="IDENTITY"/>
</id>
<field name="estadodescricao" type="string" column="EstadoDescricao" length="50" nullable="true">
  <options>
    <option name="fixed"/>
  </options>
</field>
...

Given this, I’m trying to make an action (novaAction()) to create tasks.
Here’s the Controller code:

public function novaAction(Request $request)
{
    $task = new Tasks();
    $em = $this->getDoctrine()->getManager();

    dump($task);
    #$task->setTaskEstado(new Estados());
    $form = $this->createForm(TasksType::class, $task);
    $form->handleRequest($request);

    if ($form->isSubmitted()) {
        if ($form->isValid()) {
            // Criar a tarefa na BD
            $em->persist($form->getData());
            $em->flush();
            $this->addFlash('notice', 'app.nova_tarefa.mensagem_sucesso');

            return $this->redirectToRoute('nova_tarefa');
        }

        $this->addFlash('error', 'app.nova_tarefa.mensagem_erro');
    }

And the relevant TasksType code:

class TasksType extends AbstractType
{

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('taskid', HiddenType::class)
        ...
        #->add('taskestado')
        ->add('taskestado', EntityType::class, [ 'class' => 'AppBundle:Estados' ])
        ...
}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Tasks',
        #'empty_data' => function (\Symfony\Component\Form\FormInterface $form) {
        #    #return new Tasks($form->get('tasks')->getData());
        #    return new Tasks();
        #},
    ));
}
}

The ’empty_data’ option was an attempt to create the form without passing an instance of Tasks to it.
Also, I have the same result when I add taskestado with the commented code, i. e., without arguments.

And here’s the relevant Tasks entity:

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;

/**
 * Tasks
 */
class Tasks
{
    /**
     * @var integer
     */
    protected $taskid;

    /**
     * @var \AppBundle\Entity\Estados
     */
    protected $taskestado;

    ...

    /**
     * Get taskid
     *
     * @return integer
     */
    public function getTaskid()
    {
        return $this->taskid;
    }

    /**
     * Set taskestado
     *
     * @param \AppBundle\Entity\Estados $taskestado
     *
     * @return Tasks
     */
    public function setTaskestado(\AppBundle\Entity\Estados $taskestado = null)
    {
        $this->taskestado = $taskestado;

        return $this;
    }

    /**
     * Get taskestado
     *
     * @return \AppBundle\Entity\Estados
     */
    public function getTaskestado()
    {
        return $this->taskestado;
    }

    ...
}

When I open the page, I get the following error:

Entities passed to the choice field must be managed. Maybe persist them in the entity manager?

I’d also generated the CRUD (bin/console doctrine:generate:crud --filter=Tasks), so that I could check how it’s done, but the code is similar, as well as the results (after fixing some issues in the TasksType for some datetime columns).

What am I doing wrong?

How to&Answers:

As the error says, you need to set cascade = persist in your mappings for Tasks. For example:

<many-to-one field="taskestado" target-entity="Estados" fetch="LAZY">
  <cascade>
     <cascade-persist/>
  </cascade>
  <join-columns>
    <join-column name="TaskEstado" referenced-column-name="Estado"/>
  </join-columns>
</many-to-one>

Also Instead of doing $em->persist($form->getData());
Use $em->persist($task);

See if that works…

Answer:

I finally got an answer to this, it was far from obvious.

As I’ve said in the question, there’s a many-to-one relationship from Tasks.taskestado to Estados.estado. Estados.estado is a string, that can be nullable, an empty string or (normally) a non-empty string. Although not indicated in the mapping, the empty string is the default value for taskestado.

I already have data on the table, and one of the entries for Estados.estado is precisely the empty string.

I didn’t post in the question, but I saw the following information in the stack trace of this exception:

[1] Symfony\Component\Form\Exception\RuntimeException: Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
    at n/a
        in /var/www/simpletask2/vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php line 119

    at Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader->getIdValue(object(Estados))
        in  line 

    at call_user_func(array(object(IdReader), 'getIdValue'), object(Estados))
        in /var/www/simpletask2/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php line 204

    at Symfony\Component\Form\ChoiceList\ArrayChoiceList->flatten(array(object(Estados), object(Estados), object(Estados), object(Estados), object(Estados), object(Estados), object(Estados), object(Estados)), array(object(IdReader), 'getIdValue'), array(), array(), array())
        in /var/www/simpletask2/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php line 91

So, the EntityType retrieved the values from the database and was trying to render them. In other words, they were already persisted and the problem could not really reside in how I initialized the entity.

So, I remembered about the empty string value for the Estados entity, and tried to remove it from the database: problem solved, the TaskEstado entity was initialized and rendered as expected.

Now I need a way to work around this, but the solution was found.

Thank all for your answers.

Answer:

Make sure the you have

$this->taskestado = new ArrayCollection();

in the __construct() of your Tasks class.

Then 2 notices:

Try to only use english in your code. This way you get much more responses.
As a “best practise” use Singulars for your entity class names Like Task, TaskEstado