Version 5 supported

How to encapsulate forms

Form definitions can often get long, complex and often end up cluttering up a Controller definition. We may also want to reuse the Form across multiple Controller classes rather than just one. A nice way to encapsulate the logic and code for a Form is to create it as a subclass to Form. Let's look at a example of a Form which is on our Controller but would be better written as a subclass.

// app/src/PageType/SearchPageController.php
namespace App\PageType;

use PageController;
use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\CompositeField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HeaderField;
use SilverStripe\Forms\NumericField;
use SilverStripe\Forms\OptionsetField;
use SilverStripe\Forms\RequiredFields;

class SearchPageController extends PageController
{
    // ...

    public function searchForm()
    {
        $fields = FieldList::create(
            HeaderField::create('Header', 'Step 1. Basics'),
            OptionsetField::create('Type', '', [
                'foo' => 'Search Foo',
                'bar' => 'Search Bar',
                'baz' => 'Search Baz',
            ]),
            CompositeField::create(
                HeaderField::create('Header2', 'Step 2. Advanced '),
                CheckboxSetField::create('Foo', 'Select Option', [
                    'qux' => 'Search Qux',
                ]),
                CheckboxSetField::create('Category', 'Category', [
                    'Foo' => 'Foo',
                    'Bar' => 'Bar',
                ]),
                NumericField::create('Minimum', 'Minimum'),
                NumericField::create('Maximum', 'Maximum')
            )
        );

        $actions = FieldList::create(
            FormAction::create('doSearchForm', 'Search')
        );

        $required = RequiredFields::create([
            'Type',
        ]);

        $form = Form::create($this, __FUNCTION__, $fields, $actions, $required);
        $form->setFormMethod('GET');

        $form->addExtraClass('no-action-styles');
        $form->disableSecurityToken();

        return $form;
    }

    public function doSearchForm(array $data, Form $form)
    {
        // Do something with the data, return results, or redirect
    }
}

That's quite a lot of code to include on our controller and generally makes the file look much more complex than it should be. Good practice would be to move this to a Form subclass and create a new instance for your particular controller.

// app/src/Form/SearchForm.php
namespace App\Form;

use SilverStripe\Forms\CheckboxSetField;
use SilverStripe\Forms\CompositeField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\HeaderField;
use SilverStripe\Forms\NumericField;
use SilverStripe\Forms\OptionsetField;
use SilverStripe\Forms\RequiredFields;

class SearchForm extends Form
{
    /**
     * Our constructor only requires the controller and the name of the form
     * method. We'll create the fields and actions in here.
     */
    public function __construct($controller, $name)
    {
        $fields = FieldList::create(
            HeaderField::create('Header', 'Step 1. Basics'),
            OptionsetField::create('Type', '', [
                'foo' => 'Search Foo',
                'bar' => 'Search Bar',
                'baz' => 'Search Baz',
            ]),
            CompositeField::create(
                HeaderField::create('Header2', 'Step 2. Advanced '),
                CheckboxSetField::create('Foo', 'Select Option', [
                    'qux' => 'Search Qux',
                ]),
                CheckboxSetField::create('Category', 'Category', [
                    'Foo' => 'Foo',
                    'Bar' => 'Bar',
                ]),
                NumericField::create('Minimum', 'Minimum'),
                NumericField::create('Maximum', 'Maximum')
            )
        );

        $actions = FieldList::create(
            FormAction::create('doSearchForm', 'Search')
        );

        $required = RequiredFields::create([
            'Type',
        ]);

        // now we create the actual form with our fields and actions defined
        // within this class
        parent::__construct($controller, $name, $fields, $actions, $required);

        // any modifications we need to make to the form.
        $this->setFormMethod('GET');

        $this->addExtraClass('no-action-styles');
        $this->disableSecurityToken();
    }

    /**
     * This method could be on the controller, but putting it here means we get the same
     * behaviour regardless of which controller uses this form.
     */
    public function doSearchForm(array $data, Form $form)
    {
        // Do something with the data, return results, or redirect
    }
}

Our controller will now just have to create a new instance of this form object. Keeping the file light and easy to read.

// app/src/PageType/SearchPageController.php
namespace App\PageType;

use App\Form\SearchForm;
use PageController;

class SearchPageController extends PageController
{
    private static $allowed_actions = [
        'searchForm',
    ];

    public function searchForm()
    {
        return SearchForm::create($this, __FUNCTION__);
    }
}

Related documentation

API documentation