使用来自控制器的参数来操作 zend 表单是个坏主意吗?

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

我目前正在为我的项目创建表单,并且已经在字段集上浪费了时间。首先我不得不发现你不能在字段集中定义过滤器和验证器,然后在无数次尝试在表单本身中修复它之后我放弃了它。在混合了 fieldset 表单元素和表单元素之后,我认为它对于我拥有的简单表单来说太复杂了。

所以现在我想创建一个通用表单 class 并将参数从控制器传递给它,以便只创建我需要的元素。 这是一个坏主意吗?如果是,为什么?

像这样:

控制器:

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

表格

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

我建议为不同的上下文保留不同的表单,而不是为您的整个应用程序准备一个大表单 class。根据通过 setData() 传入的数据更改表单外观非常困难,可能是导致您的字段集和验证出现问题的原因

如果你想要 "generic" 可以适应不同上下文的表单 - 这是可能的,我会像这样实现它:

<?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',
            ],
        ]);
    }
}

这是此项目中使用的通用日期时间格式:https://github.com/conferencetools/attendance-module/blob/master/src/Form/DateTimeForm.php

与您的表单的不同之处在于 $this->getOption('fieldLabel') 调用,这是将选项传递到表单中比使用构造函数更好的方法。它还在 init 方法而不是构造函数中构建表单,这对于使用选项字段和任何自定义表单元素很重要,这些元素在 class 完全构建之前不可用。

为了使其正常工作,您需要从标准服务定位器 FormPluginManager(服务名称:'Zend\Form\FormElementManager')中检索它。您可以将它注入到您的工厂控制器中,然后按如下方式使用它:

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

这不是我在上面链接的项目中的做法;我没有将 formElementManager 注入每个需要它的控制器,而是创建了一个控制器插件,使其在每个控制器中都可用。 (代码是一样的,只是用一个插件代替)

虽然 user999305 是正确的(并且应该被标记为解决方案),但看到您的评论和理解中的混淆,我想为您提供他所说内容的扩展示例。

假设你有以下情况:

  • 模块FormModule
  • 控制器FormController
  • 表格: ComposedForm

在控制器中,您有两个操作,withTitleActionwithoutTitleAction。两者都使用 ComposedForm。由于您的 FormController 中需要 ComposedForm,因此您需要使用 FormPluginManager 实例化它(您可以阅读更多相关信息 in the docs

关于如何在控制器中获取表单,现在有两种选择:

  • 通过controller plugin获取(正如user999305最后提出的,其实是个聪明的主意)
  • 你在控制器中注入 FormPluginManager

在控制器

中注入FormPluginManager

首先,您必须为控制器创建一个工厂:

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.config.php

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

现在,只需更新控制器即可接收新参数:

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;

    }
}

现在,执行两个操作:

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);
}

最后,你的表格:

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',
                ],
            ]);
        }

    }

}

希望对您有所帮助