Forms

Create complex forms in a few lines of code.

Brickrouge can create fully-functional forms in a few lines of code, and handles populating, validation, and rendering. And yes, you can use your favorite validation library.

Populating and validating forms

Provide an array of values with the Form::VALUES attribute and your form gets automatically populated, even with nested elements. The VALIDATION attribute specifies validation rules for your elements and the validate() method validates data provided by a user, and updates the ERRORS attribute.

<?php

use Brickrouge\Form;

if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
    $form[Form::VALUES] = $_POST;
    $errors = $form->validate();
}

echo $form;

The following example demonstrates how a form can be created and validated with ICanBoogie/Validate, using the Brickrouge/bind-icanboogie-validate package. An initial array of values is used to populate the form and showcase validation. A dismissible alert should be displayed with the validation errors, and invalid elements should be highlighted. When the alert is dismissed invalid elements should return to a neutral state.

Validated with URL.
YYYY-MM-DD
Validated with Regex.
Between 3 and 6 characters
Validated with BetweenLength.
<?php

namespace Brickrouge;

$form = new Form([

    Form::VALUES => $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST : [

        'email' => 'invalid-email',
        'url' => 'invalid-url',
        'birthday' => '2016-01-OI',
        'length-between' => 'abcdefg',
        'checkbox-group' => [ 2 => 'on' ],
        'radio-group' => 2

    ],

    Form::RENDERER => Form\GroupRenderer::class,
    Form::ACTIONS => [

        new Button('Submit', [

            'type' => 'submit',
            'class' => 'btn-primary'

        ])

    ],

    Element::CHILDREN => [

        'required' => new Text([

            Group::LABEL => 'Required value',
            Element::REQUIRED => true

        ]),

        'email' => new Text([

            Group::LABEL => 'Email',
            Element::VALIDATION => 'email',
            Element::DESCRIPTION => "Validated with <code>Email</code>."

        ]),

        'email_required' => new Text([

            Group::LABEL => 'Required Email',
            Element::REQUIRED => true,
            Element::VALIDATION => 'email',
            Element::DESCRIPTION => "Because it is <em>required</em>, the value is only validated when specified."

        ]),

        'url' => new Text([

            Group::LABEL => 'URL',
            Element::VALIDATION => 'url',
            Element::DESCRIPTION => "Validated with <code>URL</code>."

        ]),

        'birthday' => new Text([

            Group::LABEL => 'Date',
            Element::VALIDATION => 'regex:/^\d{4}-\d{2}-\d{2}$/',
            Element::INLINE_HELP => 'YYYY-MM-DD',
            Element::DESCRIPTION => "Validated with <code>Regex</code>."

        ]),

        'length-between' => new Text([

            Group::LABEL => 'Length min/max',
            Element::VALIDATION => 'between-length:3;6',
            Element::INLINE_HELP => 'Between 3 and 6 characters',
            Element::DESCRIPTION => "Validated with <code>BetweenLength</code>."

        ]),

        'checkbox-group' => new Element(Element::TYPE_CHECKBOX_GROUP, [

            Group::LABEL => "Checkbox group",
            Element::OPTIONS => [

                1 => "Option 1",
                "Option 2, which should initially be checked",
                "Option 3"

            ]

        ]),

        'radio-group' => new Element(Element::TYPE_RADIO_GROUP, [

            Group::LABEL => "Radio group",
            Element::OPTIONS => [

                1 => "Option 1",
                "Option 2, which should initially be checked",
                "Option 3"

            ]

        ])

    ],

    'action' => '#form-example',
    'id' => 'form-example'

]);

$form->validate();

echo $form;

Validating forms

The validate() method is used to validate data provided by the user. Errors are collected in a ErrorCollection instance and the ERRORS attribute is updated. When the form is rendered, the elements states are updated according to these errors.

<?php

if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
    $errors = $form->validate($_POST);
}

echo $form;

Validating the form is not required to display errors, you can just specify them with the ERRORS attribute.

<?php

$form[Form::ERRORS] = $_SESSION['form_errors'];

echo $form;

Validation states

The state of elements is defined by the STATE attribute, which is updated when their form is validated. Although form validation only uses STATE_DANGER for elements failing to validate, the following values are also available: STATE_SUCCESS, and STATE_WARNING. Of course, null clears the state.

<?php

namespace Brickrouge;

echo new Text([

    Group::LABEL => "Input with warning",
    Element::INLINE_HELP => "Something may have gone wrong",
    Element::STATE => Element::STATE_WARNING

]);
Something may have gone wrong
Please correct the error
Woohoo!
Something may have gone wrong
The element description remains neutral.
Please correct the error
The element description remains neutral.
Woohoo!
<?php

namespace Brickrouge;

echo new Form([

    Form::RENDERER => Form\GroupRenderer::class,

    Element::CHILDREN => [

        'inputWarning' => new Text([

            Group::LABEL => "Input with warning",
            Element::INLINE_HELP => "Something may have gone wrong",
            Element::STATE => Element::STATE_WARNING

        ]),

        'inputError' => new Text([

            Group::LABEL => "Input with error",
            Element::INLINE_HELP => "Please correct the error",
            Element::STATE => Element::STATE_DANGER

        ]),

        'inputSuccess' => new Text([

            Group::LABEL => "Input with success",
            Element::INLINE_HELP => "Woohoo!",
            Element::STATE => Element::STATE_SUCCESS

        ]),

        'radioWarning' => new Element(Element::TYPE_RADIO_GROUP, [

            Group::LABEL => "Radio group with warning",
            Element::INLINE_HELP => "Something may have gone wrong",
            Element::DESCRIPTION => "The element description remains neutral.",
            Element::STATE => Element::STATE_WARNING,
            Element::OPTIONS => [ 1 => 1, 2, 3 ]

        ]),

        'checkboxError' => new Element(Element::TYPE_RADIO_GROUP, [

            Group::LABEL => "Checkbox group with error",
            Element::INLINE_HELP => "Please correct the error",
            Element::DESCRIPTION => "The element description remains neutral.",
            Element::STATE => Element::STATE_DANGER,
            Element::OPTIONS => [ 1 => 1, 2, 3 ]

        ]),

        'textareaSuccess' => new Element('textarea', [

            Group::LABEL => "Textarea with success",
            Element::INLINE_HELP => "Woohoo!",
            Element::STATE => Element::STATE_SUCCESS,

            'rows' => 3

        ]),

    ]

]);

Using your favorite validation engine

Forms can be validated with your favorite validation engine. Forms get the validation engine through their VALIDATOR attribute or their validator getter. To provide your own validation engine you have three options: use the VALIDATOR attribute; extend the class and define a validator getter; or define a validator getter using prototype bindings.

The FormValidator class should be of great help creating your own validator.

… using the VALIDATOR attribute

<?php

use Brickrouge\Form;
use Brickrouge\Validate\ErrorCollection;
use Brickrouge\Validate\FormValidator;

class ValidateValues implements FormValidator\ValidateValues
{
    public function __invoke(array $values, array $rules, ErrorCollection $errors)
    {
        // …
    }
}

$validator = new FormValidator($this, new ValidateValues)

$form = new Form([

    Form::VALIDATOR => $validator,
    …

]);

… using inheritance

<?php

use Brickrouge\Form;
use Brickrouge\Validate\FormValidator;

class MyForm extends Form
{
    protected function lazy_get_validator()
    {
        return new FormValidator($this, new ValidateValues);
    }
}

… using prototype bindings

<?php

use Brickrouge\Form;
use Brickrouge\Validate\FormValidator;
use ICanBoogie\Prototype;

Prototype::from(Form::class)['lazy_get_validator'] = function(Form $form) {

    return new FormValidator($form, new ValidateValues);

};

Because prototype bindings apply to a class, and its subclass, you can provide different bindings for different class hierarchy. Refer to the Prototype documentation to learn more about that.

Traversing forms

Using a foreach construct, forms can be traversed recursively.

The following example demonstrates how a form can be traversed to display the labels and names of its elements.

Although children may be specified as strings, while traversing only instances of Element are returned.

foreach ($form as $element)
{
    echo "{$element[Group::LABEL]} `{$element['name']}`.\n";
}
Required value `required`. Email `email`. Required Email `email_required`. URL `url`. Date `birthday`. Length min/max `length-between`. Checkbox group `checkbox-group`. Radio group `radio-group`.

Disabling forms

A form and all of its elements can be easily disabled using the Form::DISABLED attribute.

$form[Form::DISABLED] = true;
Fork me on GitHub