将自定义和默认选项合并为在setOptionDefaults()中定义的Symfony形式的AbstractType


Merging custom and default options in Symfony form AbstractType defined in setOptionDefaults()

结构

我有一个基本控制器(ContentEditControllerBase),它可以执行编辑页面的所有标准功能。这个基本控制器由许多不同编辑页面的控制器扩展,通常只是将一些基本选项传递给基本控制器。基本控制器加载一个表单,该表单根据加载的页面类型通过自定义表单类型编辑View对象(每个编辑页面表单看起来不同)。

编辑文章本身(针对唯一内容类型的唯一捆绑包)

Route: /Admin/Article/Edit
Controller: 'Gutensite'ArticleBundle'Controller'ArticleEditController (extends ContentEditControllerBase)
FormType: 'Gutensite'ArticleBundle'Form'Type'ArticleEditType (extends ViewType)

编辑此页面的SEO字段(所有页面的标准CMS编辑字段)

Route: /Admin/Content/SEO/Edit
Controller: 'Gutensite'CmsBundle'Controller'ContentSeoEditController (extends ContentEditControllerBase)
FormType: 'Gutensite'CmsBundle'Form'Type'ContentSeoEditType (extends ViewType)   

扩展的ViewType窗体加载一个自定义ViewVersionType窗体(需要将一些选项传递给该窗体内的此关联实体)。

ContentEditControllerBase(由所有编辑页面扩展)

// the View Object is created
$view = new View;
// We determine the correct custom formType to load based on settings for the 
// particular editing page being loaded (e.g. some pages will edit the main 
// content, others the SEO settings. This will create a path like:
// 'Gutensite'CmsBundle'Form'Type'ContentSeoEditType
$formTypePath = $currentPage->getBundleInfo()['bundleNamespace']
                    .''Form'Type'''.$$currentPage->getBundleInfo()['controller']
                    .'Type';
// We then load the correct formType and pass in any options, e.g. access level to different fields (i.e. will this form show the publish button or just the save button)
$form = $this->createForm(new $formTypePath, $view, $options);

设置选项

在ViewType中,我们使用setDefaultOptions()和一些合理的默认值(请参阅下面的代码)。这允许我的控制器将选项传递给自定义表单类型。我的ViewType表单将其中一些值传递给ViewVersionType

但是,我需要能够设置默认值,然后只覆盖特定的值,所以实际上这些选项需要MERGE。目前,如果我传入自定义选项,则会覆盖整个默认值数组。

类代码

ContentSeoEditType(用额外字段扩展ViewType)

namespace Gutensite'CmsBundle'Form'Type;
use Symfony'Component'Form'FormBuilderInterface;
class ContentSeoEditType extends ViewType
{
    public function getName()
    {
        return 'contentSeoEditType';
    }
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // Enable existing options on ViewVersion object
        $options['options']['viewVersion']['enableGroup']['seo'] = TRUE;
        /**
         * Add New Inputs to View object
         */
        $builder->add('routing', 'collection', array(
            'label' => false,
            'type' => new RoutingType(),
            'allow_add' => true,
            'allow_delete' => true,
            'prototype'     => true,
            'by_reference' => false
        ));
        parent::buildForm($builder, $options);
    }

}

ViewType(包括关联表单的表单类型)

namespace Gutensite'CmsBundle'Form'Type;
use Gutensite'CmsBundle'Entity'View;
use Symfony'Component'Form'AbstractType;
use Symfony'Component'Form'FormBuilderInterface;
use Symfony'Component'OptionsResolver'OptionsResolverInterface;
class ViewType extends AbstractType
{
    public function getName()
    {
        return 'view';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Gutensite'CmsBundle'Entity'View'View',
            'cascade_validation' => true,
            // Custom Options
            'options' => array(
                // Options for viewVersion
                'viewVersion'       => array(),
                // Enabling Options
                'enableAction' => array(
                    'create'        => TRUE,
                    'save'          => TRUE,
                    'publish'       => FALSE,
                    'version'       => FALSE,
                    'duplicate'     => FALSE,
                    'delete'        => FALSE
                )
            )
        ));
    }
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // Pass Specific Variables to ViewVersion Options (when loaded below)
        $options['options']['viewVersion']['enableAction']['publish'] = $options['options']['enableAction']['publish'];

        $builder
            ->add('version', new ViewVersionType(), array(
                'label' => false,
                'required' => false,
                // Pass options to viewVersion form type
                'options' => $options['options']['viewVersion']
            ))
        ;
    }
}

ViewVersionType(自定义表单类型库)

namespace Gutensite'CmsBundle'Form'Type;
use Symfony'Component'Form'AbstractType;
use Symfony'Component'OptionsResolver'OptionsResolverInterface;
class ViewVersionType extends AbstractType
{
    public function getName()
    {
        return 'viewVersion';
    }
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'Gutensite'CmsBundle'Entity'View'ViewVersion',
            // Add a custom option bucket
            'options' => array(
                    'enableField'       => array(
                        'title'         => FALSE,
                        'versionNotes'  => FALSE,
                        'timeCustom'    => FALSE
                ),
                'enableAction'      => array(
                    'publish'       => FALSE
                ),
                'enableEntity'      => array(
                        'viewSettings'  => FALSE,
                        'content'       => FALSE
                ),
                'enableGroup'       => array(
                        'layout'        => FALSE,
                        'seo'           => FALSE
                )
            )
        ));
    }
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // **********************************
        // ERROR: $options['options'] does NOT have all the default options listed in setDefaultOptions()
        // Only has $options['options']['enableGroup']['seo'] = 1
        // Only has $options['options']['enableAction']['publish'] = 1
        // **********************************

    }
}

问题摘要

如何在formType上设置默认选项,并允许我的控制器传入特定的自定义选项以仅覆盖该选项,即将我的自定义选项与默认选项合并。我需要能够将选项从ViewType传递到从ViewType加载的子窗体ViewTypeVersion(相同的合并)。

默认选项不用于合并。这是预期的行为。它们被称为默认值,因为它们只有在您没有定义它们的情况下才存在。你可以在这里阅读更多关于界面的信息

以下是覆盖OptionResolver:中选项的代码部分

// Make sure this method can be called multiple times
$combinedOptions = clone $this->defaultOptions;
// Override options set by the user
foreach ($options as $option => $value) {
    $combinedOptions->set($option, $value);
}

不需要在"选项"下定义数组,而是使用自己的唯一索引。

// These options will be available for you
$resolver->setDefaults(array(
    'enableAction' => array('publish' => FALSE),
    'enableEntity' => array(
        'viewSettings'  => FALSE,
        'content'       => FALSE
     ),
));

创建表单并使其共享选项的另一种灵活方法是将其作为服务创建。这样,您就可以随心所欲地共享配置中的选项。

最后,扩展FormType与扩展类不同。当您像对ContentSeo表单类型那样扩展类时,Symfony无法解析选项和依赖项。FormType通过在类中定义getParent()方法来相互继承。在一个类中使用getParent()方法,让symfony知道这种形式从另一个继承,并且选项将被正确解析。

您的主要问题是两个问题:

  • 首先,您正在尝试使用options,这是我的symfony已经使用过的,如果您深入研究代码,您会看到symfony在尝试合并任何内容之前检查它是否定义了。您应该使用自己的索引键,而不是symfony默认使用的索引键。

  • 其次,在扩展表单类型时需要使用getParent,而不是扩展类。

试着做这两个改变,看看它是如何为你工作的。我认为这应该是一个良好的开端。

希望这有助于澄清一些问题。

?为什么不覆盖自定义FormTypes中的buildView函数?

$view变量有一个名为vars的属性,其中包含选项(解析后),您可能需要使用实际合并的值更新这些选项。

看看这个例子:

class MyFormType extends AbstractType
{
    //some code ...
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['options'] = array_merge($this->fixedOptions,$view->vars['options']);
    }
}

```