简体   繁体   中英

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

Structure

I have a base controller ( ContentEditControllerBase ) that does all the standard functionality for editing a page. This base controller is extended by the controllers of many different edit pages, often simply passing in some basic options to the base controller. The base controller loads a form that edits a View object via a custom formType based on the type of page that is loaded (each edit page form looks different).

Edit the Article Itself (unique bundle for unique content type)

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

Edit the SEO Fields for this page (standard CMS editing fields for all pages)

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

The ViewType form that is extended loads a custom ViewVersionType form (some options need to get passed to this associated entity inside that form).

ContentEditControllerBase (extended by all editing pages)

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

Setting Options

Inside the ViewType we use setDefaultOptions() , with some sensible default values (see code below). This allows my controller to pass in options to the custom form type. And my ViewType form passes on some of those values to the ViewVersionType .

However, I need to be able to set default values, and then overwrite only specific values, so in effect these options need to MERGE . Currently, if I pass in custom options, the entire array of default values is overwritten.

The Classes Code

ContentSeoEditType (Extends the ViewType with extra fields)

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 (the form type that includes associated forms)

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 (Custom Form Type base)

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
        // **********************************


    }
}

SUMMARY OF THE PROBLEM

How can I set default options on the formType, and also allow my controller to pass in specific custom options to overwrite only that option, ie merge my custom options with the default options. I need to be able to pass options from ViewType to sub form ViewTypeVersion that is loaded from ViewType as well (same merging).

Default options are not made to merge. That's the expected behavior. They are called default because they only exist if you did not define them. You can read more about the interface here

Here is the part of the code that overrides your options in 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);
}

There is no need to define your arrays under "options" use your own unique indexes.

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

Another flexible way of creating forms and making them share options is by creating them as a service . That way you can share the options in the config as you wish.

Finally, extending FormType is not the same as extending classes. Symfony can not resolve options and dependency when you extend classes as you did for ContentSeo form type. FormType inherit from each other by defining the getParent() method in the class. Using the getParent() method in one class let symfony knows that this form inherit from the other and the options will be resolved correctly.

Your main problem here is two issues:

  • First, you are trying to use options which is used my symfony already and if you dig into the code you will see that symfony checks to see if it defined or not before trying to merge anything. You should be using your own index keys and not one that symfony uses by default.

  • Second, You need to use getParent when extending a form type rather than extending the class.

Try with these two changes and see how it works for you. I believe that should be a good start.

Hope that helps clear some questions.

¿Why don't you overwrite buildView function in your custom FormTypes ?

The $view variable, has a property called vars which contains the options (once resolved), whose you may need to update with an actually merged value.

Take a look a this example:

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

}

```

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM