2

I have an evaluation that has different questions (items) and each item has a specific number of possible choices.

I need to pass the number of choices and the labels of each question to build the form for each item (question).

I can pass variables from the "parent" form to the form collection but I can't figure out how to pass a variable that is specific to each iteration in the Evaluation "answers" property.

In my controller:

$evaluation = new Evaluation();
$answers = array();
// i set each answer to default 0, for the form to place them in view
foreach ($items as $index => $item) {
    $answers[$index] = array('value' => 0, 'item_id' => $item->getId());
}
$evaluation->setAnswers($answers);

$formBuilder = $this->createFormBuilder($evaluation);

$formBuilder->add("answers", CollectionType::class, array(
    'entry_type' => AnswerType::class,
    //here i pass all the items objects array to the AnswerType form.
    'entry_options' => array(
        'items' => $items,
    ),
    'allow_delete' => false,
    'allow_add' => false,
));

And than in the AnswerType form class:

class AnswerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $choices = array();

        // here I have all the items available in $options['items'];
        // but each item has a specific number of choices, and I need the current one that is being built
        // and I also need other info form the item like the question it self

        for($i = 1; $i <= $current_item_number_of_choices_that_i_dont_have;$i++){
            $choices[$i] = $i;
        }

        $builder->add('value', ChoiceType::class, array(
            'choices_as_values' => true,
            'multiple'=>false,
            'expanded'=>true,
            'choices' => $choices,
            'label' => $current_item_text_label_that_I_also_need
        ));

    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'items' => null
        ));
    }
}

Thanks! :)

yceruto
  • 9,230
  • 5
  • 38
  • 65
Vlad Dogarescu
  • 153
  • 1
  • 11
  • Actually I found a "property_path" in the $options array, that is passed as stringified version of the index of the answers array "[0]","[1]","[2]"... that I can parse and than access the items array using it. But seems very sketchy... there must be an "official" way.. – Vlad Dogarescu May 06 '17 at 18:02
  • Where are the choices in your AnswerType class coming from? – ehymel May 06 '17 at 23:22
  • I am generating them there, form 1 to the max of the current item, that is dynamic.. – Vlad Dogarescu May 07 '17 at 05:20
  • So your choices are '1', '2', '3', '4', etc? But you have a label for each one? I guess I should have asked where do your labels come from. If from a database then it seems you are making this more complicated that needed, but then maybe I don't understand your question. – ehymel May 07 '17 at 21:58
  • So, for item 1 the choices are 1,2 and 3, but for item 2 the choices are 1,2,3,4,5, for item 3 they are 1....9 and so on.. The labels are also 1,2,3.. – Vlad Dogarescu May 08 '17 at 05:12

1 Answers1

2

Likely a CollectionType is not the type you need now to achieve it, because it hides a layer you need to handle (the current item). So the best would be add the answer collection yourself. Something like this:

class AnswerCollectionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        foreach ($options['items'] as $index => $item) {
            $builder->add($index + 1, AnswerType::class, [
                'item' => $item,
            ]);
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired('items');
    }
}

class AnswerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $item = $options['item'];

        $choices = [];
        for ($i = 1; $i <= $item->getNumChoices(); $i++) {
            $choices[$i] = $i;
        }

        $builder->add('value', ChoiceType::class, [
            'label' => $item->getLabel(),
            'choices' => $choices,
            'expanded' => true,
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired('item');
    }
}

Note that AnswerType now requires the item option necessary to build the current choice form correctly.

$formBuilder->add('answers', AnswerCollectionType::class, [
    'items' => $items,
])

In the other hand, the CollectionType's workaround is more complex (by using Events) needing a static internal counter to know the current item.

See http://symfony.com/doc/current/form/dynamic_form_modification.html for details.

yceruto
  • 9,230
  • 5
  • 38
  • 65