Botman
Botman

Reputation: 81

Dynamic generated form with Zend Framework 2

I'm creating ZF2 Poll Module. I have poll with many questions. Every question has answers that can be multiple answers or single answer(Radio or MultiCheckbox). How to create a dynamic form that I can show to front-end?

This is what I've tried, but the form doesn't validate correctly...

module\Polls\src\Polls\Form\PollFillingQuestionsForm.php

  <?php
    namespace Polls\Form;

    use Zend\Form\Form;
    use Polls\Form\Fieldset\PollFillingQuestionAnswerFieldset;
    use Polls\Form\Fieldset\PollFillingQuestionFieldset;

    class PollFillingQuestionsForm extends Form {

    public function __construct($questionsObject) {
        parent::__construct('questionsForm');

        $questionsFieldset = new PollFillingQuestionFieldset('questions');

    //$questionsObject is array of question objects.
        foreach ($questionsObject as $questionObject) {
            $fieldset = new PollFillingQuestionAnswerFieldset($questionObject->id, array(), $questionObject);
                $questionsFieldset->add($fieldset);
            }

            $this->add($questionsFieldset);

            $this->add(array(
                'name' => 'submit',
                'attributes' => array(
                    'type' => 'submit',
                    'value' => 'Submit Poll',
                    'class' => 'btn btn-success',
                ),
            ));
        }
    }

module\Polls\src\Polls\Form\Fieldset\PollFillingQuestionAnswerFieldset.php

<?php

namespace Polls\Form\Fieldset;

use Polls\Model\QuestionAnswer;
use Zend\Form\Fieldset;
use Zend\Stdlib\Hydrator\ArraySerializable;

class PollFillingQuestionAnswerFieldset extends Fieldset {

public function __construct($name, $options, $questionObject) {
    parent::__construct($name, $options);

    $question = $questionObject;

    $this->setLabel($question->title);

    $type = 'Radio';
    $elementType = 'radio';
    switch ($question->answer_type) {
        case 'many':
            $type = 'MultiCheckbox';
            $elementType = 'checkbox';
            break;
        case 'one':
            $type = 'Radio';
            $elementType = 'radio';
            break;
        default:
            $type = 'Radio';
            $elementType = 'radio';
            break;
    }
    $this->setHydrator(new ArraySerializable())
            ->setObject(new QuestionAnswer());

    $answers = $question->getAnswers();

    $answerValues = array();
    foreach ($answers as $answer) {
        $answerValues[$answer->id] = $answer->title;
    }
    $this->add(array(
        'name' => 'answer',
        'type' => $type,
        'options' => array(
            'type' => $elementType,
            'value_options' => $answerValues,
        ),
    ));
}
}

Upvotes: 2

Views: 1061

Answers (1)

Saeven
Saeven

Reputation: 2300

I've done this in the past, with a clean Factory strategy you can inject the dependencies into your form and your input filter. The magic lies in your Factories.

Start by wiring things in your service manager config:

'form_elements' => [
    'factories' => [
        DynamicForm::class => DynamicFormFactory::class,
    ],
],

'input_filters' => [
    'factories' => [
        DynamicInputFilter::class => DynamicInputFilterFactory::class,          
    ],
],

First task is to get your FormFactory done up right.

class DynamicFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{

    /**
     * @var array
     */
    protected $options;

     /**
     * Set creation options
     *
     * @param  array $options
     * @return void
     */
    public function setCreationOptions( array $options )
    {
        $this->options = $options;
    }


    /**
     * {@inheritdoc}
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        /**
         * @var \Zend\Form\FormElementManager       $serviceLocator
         * @var \Zend\ServiceManager\ServiceManager $serviceManager
         */
        $serviceManager = $serviceLocator->getServiceLocator();
        try
        {
            $options   = /* set up your form's config, you have the service manager here */;
            $form      = new DynamicForm( $options );

            $form->setInputFilter( $serviceManager->get('InputFilterManager')->get( DynamicFormFilter::class, $options ) );
        }
        catch( \Exception $x )
        {
            die( $x->getMessage() );
        }
        return $form;
    }       
}

Then, react to $options in your DynamicInputFilterFactory through the MutableCreationOptionsInterface implementation. You generally don't want forms and filters to be 'option aware', let the factories take care of that.

class DynamicInputFilterFactory implements FactoryInterface, MutableCreationOptionsInterface
{

    protected $options;

    /**
     * Set creation options
     *
     * @param  array $options
     * @return void
     */
    public function setCreationOptions( array $options )
    {
        $this->options = $options;
    }

    public function createService( ServiceLocatorInterface $serviceLocator )
    {
        /* do stuff with $this->options */

        return new DynamicInputFilter(
             /* pass your transformed options */
        );
    }   
}

Next, all you have to do is create your form and input filter per what was passed to them through MutableOptions. Set your dependencies in __construct (don't forget to call parent::__construct) and initialize your form in init per the options passed in.

I suspect you have a good base in ZF2, so I'll stop here. This ought to get you on your way. Take-aways are MutableCreationOptionsInterface and separating your InputFilter and Form construction, combining the two in your Form Factory.

Upvotes: 1

Related Questions