Is it a bad idea to manipulate zend forms with parameters from the controller?

65 Views Asked by At

I'm currently creating forms for my project and already wasted time with fieldsets. First I had to find out that you can't define filters and validators in fieldsets and then after countless tries to fix that in the form itself I gave up on it. After having a mixed bag of fieldset form elements and form elements I decided that it's too complicated for the simple forms I have.

So now I thought I'd create a general Form class and pass parameters from the controller to it in order to only create the elements I need. Is this a bad idea? And if yes, why?

Something like this:

Controller:

$include_title = true
$screening_form->setData($include_title);

Form

public function __construct($include_title)
{
    if ($include_title)
    {
        $this->add([
           'type' => 'text',
           'name' => 'title',
           'options' => [
                'label' => 'text',
                'placeholder' => 'Test',
            ],
        ]);
    }
}
2

There are 2 best solutions below

2
user999305 On BEST ANSWER

I would suggest retaining different forms for different context's don't have one big Form class for your whole application. It's quite difficult to change the form appearance based on data passed in via setData() and might have been what was causing you issues with your fieldsets and validation

If you wanted to have "generic" forms which are adaptable to different context's - that is possible I'd implement it like this:

<?php
namespace ConferenceTools\Attendance\Form;

use Zend\Form\Element\DateTime;
use Zend\Form\Element\Submit;
use Zend\Form\Form;

class DateTimeForm extends Form
{
    public function init()
    {
        $this->add([
            'type' => DateTime::class,
            'name' => 'datetime',
            'options' => [
                'label' => $this->getOption('fieldLabel'),
            ],
            'attributes' => [
                'class'=> 'datetimepicker-input',
                'id' => "dtpicker",
                'data-toggle' => "datetimepicker",
                'data-target' => "#dtpicker",
                'autocomplete' => 'off',
            ],
        ]);
    }
}

This is a generic date time form use in this project: https://github.com/conferencetools/attendance-module/blob/master/src/Form/DateTimeForm.php

The differences to your form are the $this->getOption('fieldLabel') call, this is a better way to pass options into the form than using the constructor. It also builds the form in the init method instead of the constructor, this is important to make use of the options field and any custom form elements which won't be available until the class has been fully constructed.

For this to work properly, you need to retrieve it from the FormPluginManager (service name: 'Zend\Form\FormElementManager') which is a standard service locator. You can inject this into your controller in it's factory and then use it as follows:

// in controller method
$form = $this->formElementManager->get(FormName::class, ['fieldLabel' => 'Arrival time']);

This is not quite how I do it in the project linked above; instead of injecting the formElementManager into every controller that needed it, I created a controller plugin which makes it available in every controller. (The code is the same, just in a plugin instead)

1
Ermenegildo On

Although user999305 is correct (and deserve to be marked as solution), seeing your comment and the confusion in the understaning, I'd like to give you an extended example of what he said.

Let's you have the following situation:

  • Module: FormModule
  • Controller: FormController
  • Form: ComposedForm

In the controller, you have two actions, withTitleAction and withoutTitleAction. Both use ComposedForm. Since you need ComposedForm in your FormController, you need to instantiate it using FormPluginManager (you can read more about it in the docs)

You have now two options on how to get the form in your controller:

  • you get it through a controller plugin (as proposed at the end by user999305, and which is actually a smart idea)
  • you inject the FormPluginManager in your controller

Inject FormPluginManager in controller

First off, you must create a factory for the controller:

Controller/FormControllerFactory.php

namespace FormModule\Controller;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class FormControllerFactory implements FactoryInterface {

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
        $formPluginManager = $container->get('FormElementManager');
        return new FormController($formPluginManager);

    }

}

Module's configuration must be updated, in order for the application to know how to create the controller

module.config.php

'controllers' => [
    'factories' => [
        Controller\FormController::class => Controller\FormControllerFactory::class
    ]
],

And now, just update the controller to receive the new parameter:

Controller/FormController.php

namespace FormModule\Controller;

use Zend\Form\FormElementManager;
use Zend\Mvc\Controller\AbstractActionController;

class FormController extends AbstractActionController {

    private $formManager;

    public function __construct(FormElementManager $formManager) {
        $this->formManager = $formManager;

    }
}

Now, implement the two actions:

Controller/FormController.php

public function withTitleAction(){
    $options = ['hasTitle' => true];
    $form = $this->formManager->get(\FormModule\Form\ComposedForm::class, $options);
}

public function withoutTitleAction(){
    $options = ['hasTitle' => false];
    $form = $this->formManager->get(\FormModule\Form\ComposedForm::class, $options);
}

Finally, your form:

Form/ComposedForm.php

namespace FormModule\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;

class ComposedForm extends Form {

    public function __construct($name = null, $options = []) {
        // DO NOT FORGET THIS
        parent::__construct($name, $options);

        // The constructor is not really needed, unless you have some
        // dependencies or you have to do something particular, like
        // setting the hydrator, form attributes, form object, ...

    }

    public function init() {
        parent::init();

        if ($this->getOption('hasTitle')) {
            $this->add([
                'type' => 'text',
                'name' => 'title',
                'options' => [
                    'label' => 'text',
                    'placeholder' => 'Test',
                ],
            ]);
        }

    }

}

Hope this will help you