skrilled
skrilled

Reputation: 5371

symfony2 form, collection of objects, issue updating existing object property

The form consists of one question which has several answers, so that the answers can be dynamically created for each question. This stuff all works fine:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('question','textarea')
        ->add('answers', 'collection', array(
            'type'=>new AnswerType(),
            'allow_add'=>true,
            'allow_delete'=>true,
            'label' => false
        ))
    ;
}

Here is form code for AnswerType:

    $builder
        ->add('answer','text', array(
            'attr'=>array(
                'class'=>'form-control'
            ),
            'label'=>false
        ))
        ->add('isGoodAnswer', 'checkbox', array(
            'label'=>'Good?',
            'required'=>false
        ))
    ;

I am using prototype template to populate container via jquery.

Adding new answer objects to the question object works fine. Deleting answers is also not a problem.

However, if I go to update existing property on one of the collection form inputs it does not update the existing object. It is persisting the question object though as it will update the text of the question itself. I can only delete and create new to replace something currently and I am having a hard time figuring out why.

Here is snippet of code from template form that is submitted:

    <ul id="answer-fields-list" data-prototype="{{ form_widget(form.answers.vars.prototype)|e }}">
    {% for answer in form.answers %}
        <li>
            <div class='col-md-12'>                 
                {{ form_widget(answer) }}                   
                <div>
                    <a href='#' class='btn btn-sm btn-danger delete-this'><span class='glyphicon glyphicon-trash'></span></a>
                </div>
            </div>                  
        </li>
    {% endfor %}
    </ul>           
    <a href="#" id="add-answer" class='btn btn-sm btn-success'><span class='glyphicon glyphicon-plus-sign'></span> Add Answer</a>           

edit, here is full controller code for this update method:

    $question = $em->getRepository('ChecklistMainBundle:ChecklistQuestion')->findOneById($questionId);
    if(!$question) throw new NotFoundHttpException('Question not found');

    $form = $this->createForm(new QuestionAnswerType(), $question);

    $form->handleRequest($request);

    if($request->getMethod()=='POST' && $form->isValid())
    {
        if($form->get('attachment')->getData() != null) {
            $question->uploadAttachment();
        }
        $em->persist($question);
        $em->flush();

        $this->get('session')->getFlashBag()->add('success', 'Question was modified successfully!');

        return $this->redirect($this->generateUrl('admin_checklists_view', array('id'=>$id)));
    }

Upvotes: 5

Views: 8338

Answers (5)

skrilled
skrilled

Reputation: 5371

After searching through Google for hours I ran across a duplicate question that resembles mine.

How to force Doctrine to update array type fields?

I adjusted by setAnswers method as follows to reflect this answer:

 public function setAnswers($answers)
    {
        if(!empty($answers) && $answers === $this->answers) {
            reset($answers);
            $answers[key($answers)] = clone current($answers);
        }
        $this->answers = $answers;
        return $this;
    }

It now saves existing answers fine, no more issues :)

Upvotes: 4

Florian Klein
Florian Klein

Reputation: 8915

You may want to compare the object graph before and after the handleRequest call.

I advise you to use \Doctrine\Common\Util\Debug::dump();, it will make output shorter.

Another thing to check is the raw request data itself, to see if the submitted data itself is correct: just use the form web debug toolbar tab (or request, depending).

Now why it behaves like that is difficult to answer. The collection type behaves very differently given you configure it with by_reference or not:

http://symfony.com/doc/current/reference/forms/types/collection.html

if you're using the collection form type where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the setter (e.g. setAuthors()) to be called.

and the https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php is definitly playing a big role here. As you can see, ordering is important.

Verify that the submitted data is in the expected order.

Last (or maybe check this first :) ), most of the time collection just works :) Verify twice that your getters and setters don't contain a tricky typo that messes up the whiole thing:

Try manually if needed, by mimicking what would do the form component.

Hope it helps!

Upvotes: 1

Waaghals
Waaghals

Reputation: 2609

Add both addAnswer() and removeanswer() to your Question entity/model.

Set the collection's by_reference option to false

$builder
        ->add('question','textarea')
        ->add('answers', 'collection', array(
            'type'=>new AnswerType(),
            'allow_add'=>true,
            'allow_delete'=>true,
            'label' => false,
            'by_reference' = > false
        ))
    ;

Upvotes: 2

devilcius
devilcius

Reputation: 1884

I think you should be using merge to update the object:

$em->merge($question)
$em->flush();

Upvotes: 1

Richard
Richard

Reputation: 4119

Answers won't persist themselves.

Either:

foreach ($question->getAnswers() as $answer) {

    $em->persist($answer);
}

$em->persist($question);
$em->flush();

Or (in your question entity):

/**
 * @ORM\OneToMany(targetEntity="YourBundle\Etc\Entity\Answer",mappedBy="question",cascade={"persist"})
 */

More info.

Upvotes: 1

Related Questions