tobento/service-form

Building HTML forms easily.

1.0.2 2024-03-15 18:45 UTC

This package is auto-updated.

Last update: 2024-11-15 20:11:20 UTC


README

Buliding HTML forms.

Table of Contents

Getting started

Add the latest version of the html service project running this command.

composer require tobento/service-form

Requirements

  • PHP 8.0 or greater

Highlights

  • Framework-agnostic, will work with any project
  • Decoupled design

Documentation

Create Form

use Tobento\Service\Form\Form;
use Tobento\Service\Form\InputInterface;
use Tobento\Service\Form\TokenizerInterface;
use Tobento\Service\Form\ActiveElementsInterface;
use Tobento\Service\Message\MessagesInterface;

$form = new Form(
    input: null, // null|InputInterface
    tokenizer: null, // null|TokenizerInterface
    activeElements: null, // null|ActiveElementsInterface
    messages: null // null|MessagesInterface
);

Parameters explanation

Form Factory

You may use form factories to create the form class.

ResponserFormFactory

Firstly, make sure you have the responser service installed:

composer require tobento/service-responser

Check out the Responser Service to learn more about it in general.

use Tobento\Service\Form\FormFactoryInterface;
use Tobento\Service\Form\ResponserFormFactory;
use Tobento\Service\Form\ActiveElementsInterface;
use Tobento\Service\Form\Form;
use Tobento\Service\Responser\ResponserInterface;

$formFactory = new ResponserFormFactory(
    responser: $responser, // ResponserInterface
    tokenizer: null, // null|TokenizerInterface
    activeElements: null, // null|ActiveElementsInterface
);

var_dump($formFactory instanceof FormFactoryInterface);
// bool(true)


$form = $formFactory->createForm();

var_dump($form instanceof Form);
// bool(true)

Form Elements

Form

<?= $form->form() ?>
// <form method="POST">

<?= $form->close() ?>
// </form>

form attributes

<?= $form->form(attributes: ['method' => 'GET']) ?>
// <form method="GET">

Input

<?= $form->input(
    name: 'name',
    type: 'text',
    value: 'value',
    attributes: [],
    selected: null,
    withInput: true,
) ?>
// <input name="name" id="name" type="text" value="value">

Parameters explanation

Checkbox Type

<?= $form->input(
    name: 'colors[]',
    type: 'checkbox',
    value: 'red',
    attributes: ['id' => 'colors_red'],
    selected: ['red'], // or 'red'
) ?>
// <input id="colors_red" name="colors[]" type="checkbox" value="red" checked>

checkboxes

You may use the checkboxes method to create multiple checkbox input elements with its labels:

<?= $form->checkboxes(
    name: 'colors',
    items: ['red' => 'Red', 'blue' => 'Blue'],
    selected: ['blue'],
    attributes: [],
    labelAttributes: [],
    withInput: true,
    wrapClass: 'form-wrap-checkbox'
) ?>
/*
<span class="form-wrap-checkbox">
    <input id="colors_1" name="colors[]" type="checkbox" value="red">
    <label for="colors_1">Red</label>
</span>
<span class="form-wrap-checkbox">
    <input id="colors_2" name="colors[]" type="checkbox" value="blue" checked>
    <label for="colors_2">Blue</label>
</span>
*/

Parameters explanation

checkboxes with each

You may use the each method if you have an array of objects or just to have more control over the value, label and array key for the name of the checkbox.

$items = ['red' => 'Red', 'blue' => 'Blue'];

<?= $form->checkboxes(
    name: 'colors',
    items: $form->each(items: $items, callback: function($item, $key): array {
        // value:string, label:string|null, array-key:string|null
        return [$key, strtoupper($item), $key];
    }),
    selected: ['blue'],
) ?>
/*
<span class="form-wrap-checkbox">
    <input id="colors_red" name="colors[red]" type="checkbox" value="red">
    <label for="colors_red">RED</label>
</span>
<span class="form-wrap-checkbox">
    <input id="colors_blue" name="colors[blue]" type="checkbox" value="blue" checked>
    <label for="colors_blue">BLUE</label>
</span>
*/

Radio Type

<?= $form->input(
    name: 'colors',
    type: 'radio',
    value: 'red',
    attributes: ['id' => 'colors_red'],
    selected: 'red',
) ?>
// <input id="colors_red" name="colors" type="radio" value="red" checked>

radios

You may use the radios method to create multiple radio input elements with its labels:

<?= $form->radios(
    name: 'colors',
    items: ['red' => 'Red', 'blue' => 'Blue'],
    selected: 'blue',
    attributes: [],
    labelAttributes: [],
    withInput: true,
    wrapClass: 'form-wrap-radio'
) ?>
/*
<span class="form-wrap-radio">
    <input id="colors_1" name="colors[]" type="radio" value="red">
    <label for="colors_1">Red</label>
</span>
<span class="form-wrap-radio">
    <input id="colors_2" name="colors[]" type="radio" value="blue" checked>
    <label for="colors_2">Blue</label>
</span>
*/

Parameters explanation

radios with each

You may use the each method if you have an array of objects or just to have more control over the value and label.

$items = ['red' => 'Red', 'blue' => 'Blue'];

<?= $form->radios(
    name: 'colors',
    items: $form->each(items: $items, callback: function($item, $key): array {
        // value:string, label:string|null
        return [$key, strtoupper($item)];
    }),
    selected: 'blue',
) ?>
/*
<span class="form-wrap-radio">
    <input id="colors_red" name="colors" type="radio" value="red">
    <label for="colors_red">RED</label>
</span>
<span class="form-wrap-radio">
    <input id="colors_blue" name="colors" type="radio" value="blue" checked>
    <label for="colors_blue">BLUE</label>
</span>
*/

Label

<?= $form->label(
    text: 'Text',
    for: 'colors',
    attributes: [],
    requiredText: '',
    optionalText: '',
) ?>
// <label for="colors">Text</label>

Parameters explanation

with required text

<?= $form->label(
    text: 'Text',
    requiredText: 'required',
) ?>
// <label>Text<span class="required">required</span></label>

with optional text

<?= $form->label(
    text: 'Text',
    optionalText: 'optional',
) ?>
// <label>Text<span class="optional">optional</span></label>

Select

<?= $form->select(
    name: 'colors[]',
    items: ['red' => 'Red', 'blue' => 'Blue'],
    selected: ['blue'],
    selectAttributes: ['multiple'],
    optionAttributes: [],
    optgroupAttributes: [],
    emptyOption: null,
    withInput: true,
) ?>
/*
<select multiple name="colors" id="colors">
    <option value="red">Red</option>
    <option value="blue" selected>Blue</option>
</select>
*/

Parameters explanation

with optgroup elements

<?= $form->select(
    name: 'roles',
    items: [
        'Frontend' => [
            'guest' => 'Guest',
            'registered' => 'Registered',
        ],
        'Backend' => [
            'editor' => 'Editor',
            'administrator' => 'Aministrator',
        ],        
    ],
) ?>
/*
<select name="roles" id="roles">
    <optgroup label="Frontend">
        <option value="guest">Guest</option>
        <option value="registered">Registered</option>
    </optgroup>
    <optgroup label="Backend">
        <option value="editor">Editor</option>
        <option value="administrator">Aministrator</option>
    </optgroup>
</select>
*/

with each method

You may use the each method if you have an array of objects or just to have more control over the value, label and array key for the name of the radio.

$items = ['red' => 'Red', 'blue' => 'Blue'];

<?= $form->select(
    name: 'colors',
    items: $form->each(items: $items, callback: function($item, $key): array {
        // value:string, label:string|null
        return [$key, strtoupper($item)];
    }),
    selected: 'blue',
) ?>
/*
<select name="colors" id="colors">
    <option value="red">RED</option>
    <option value="blue" selected>BLUE</option>
</select>
*/

with empty option

<?= $form->select(
    name: 'colors',
    items: ['red' => 'Red', 'blue' => 'Blue'],
    emptyOption: ['none', '---'],
) ?>
/*
<select name="colors" id="colors">
    <option value="none">---</option>
    <option value="red">Red</option>
    <option value="blue">Blue</option>
</select>
*/

Textarea

<?= $form->textarea(
    name: 'description',
    value: 'Lorem ipsum',
    attributes: [],
    withInput: true,
) ?>
// <textarea name="description" id="description">Lorem ipsum</textarea>

Parameters explanation

Button

<?= $form->button(
    text: 'Submit Text',
    attributes: [],
    escText: true,
) ?>
// <button type="submit">Submit Text</button>

Parameters explanation

Fieldset And Legend

<?= $form->fieldset(
    legend: 'Legend',
    attributes: [],
    legendAttributes: [],
) ?>
// <fieldset><legend>Legend</legend>

<?= $form->fieldsetClose() ?>
// </fieldset>

Parameters explanation

Datalist

<?= $form->datalist(
    name: 'colors',
    items: ['red', 'blue'],
    attributes: [],
) ?>
/*
<datalist id="colors">
    <option value="red"></option>
    <option value="blue"></option>
</datalist>
*/

Parameters explanation

with each method

You may use the each method if you have an array of objects or just to have more control over the value.

$items = ['red' => 'Red', 'blue' => 'Blue'];

<?= $form->datalist(
    name: 'colors',
    items: $form->each(items: $items, callback: function($item, $key): array {
        // value:string
        return [strtoupper($item)];
    })
) ?>
/*
<datalist id="colors">
    <option value="RED"></option>
    <option value="BLUE"></option>
</datalist>
*/

Option

<?= $form->option(
    value: 'red',
    text: 'Red',
    attributes: [],
    selected: ['red'], // or 'red'
) ?>
// <option value="red" selected>Red</option>

Parameters explanation

Input Data

The input data is used to repopulate the values of the form elements.

Create Input

use Tobento\Service\Form\InputInterface;
use Tobento\Service\Form\Input;

$input = new Input(array_merge(
    $_GET ?? [],
    $_POST ?? [],
));

var_dump($input instanceof InputInterface);
// bool(true)

Now, the value of the form elements gets automatically repopulated if a input data for the specified element exists.

Form Input Methods

You may use the following methods to access the input data on the form class.

getInput

use Tobento\Service\Form\Form;
use Tobento\Service\Form\Input;

$form = new Form(
    input: new Input(['color' => 'red']),
);

$value = $form->getInput(
    name: 'color',
    default: 'blue',
    withInput: true, // default
);

var_dump($value);
// string(3) "red"

$value = $form->getInput(
    name: 'color',
    default: 'blue',
    withInput: false,
);

var_dump($value);
// string(4) "blue"

hasInput

use Tobento\Service\Form\Form;
use Tobento\Service\Form\Input;

$form = new Form(
    input: new Input(['color' => 'red']),
);

var_dump($form->hasInput('color'));
// bool(true)

withInput

Returns a new Form instance with the specified input.

use Tobento\Service\Form\Form;
use Tobento\Service\Form\Input;
use Tobento\Service\Form\InputInterface;

$form = new Form(
    input: new Input(['color' => 'red']),
);

$form = $form->withInput(
    input: null, // null|InputInterface
);

Tokenizer

The tokenizer is used to generate and verify tokens to protect your application from cross-site request forgeries.

Session Tokenizer

Firstly, make sure you have the session service installed:

composer require tobento/service-session

Check out the Session Service to learn more about it in general.

use Tobento\Service\Form\TokenizerInterface;
use Tobento\Service\Form\SessionTokenizer;
use Tobento\Service\Session\SessionInterface;
use Tobento\Service\Session\Session;

$session = new Session(name: 'sess');

$tokenizer = new SessionTokenizer(
    session: $session, // SessionInterface
    tokenName: 'csrf', // default
    tokenInputName: '_token' // default
);

var_dump($tokenizer instanceof TokenizerInterface);
// bool(true)

Tokenizer Methods

setTokenName

$tokenizer->setTokenName('csrf');

getTokenName

var_dump($tokenizer->getTokenName());
// string(4) "csrf"

setTokenInputName

$tokenizer->setTokenInputName('_token');

getTokenInputName

var_dump($tokenizer->getTokenInputName());
// string(6) "_token"

get

Return the token for the specified name.

var_dump($tokenizer->get('csrf'));
// Null or string

generate

Generates and returns a new token for the specified name.

var_dump($tokenizer->generate('csrf'));
// string(40) "token-string..."

delete

Delete the token for the specified name.

$tokenizer->delete('csrf');

verifyToken

$isValid = $tokenizer->verifyToken(
    inputToken: 'input token string', // string
    name: 'csrf', // null|string The name of the token to verify.
);

// or set a token.
$isValid = $tokenizer->verifyToken(
    inputToken: 'input token string', // string
    token: 'token string' // null|string
);

Tokenizer PSR-15 Middleware

You may also use the VerifyCsrfToken::class middleware to verify the form token.
If token is invalid, a InvalidTokenException will be thrown.

use Tobento\Service\Form\Middleware\VerifyCsrfToken;
use Tobento\Service\Form\InvalidTokenException;

$middleware = new VerifyCsrfToken(
    tokenizer: $tokenizer, // TokenizerInterface
    name: 'csrf', // default
    inputName: '_token', // default
    headerTokenName: 'X-Csrf-Token', // default. Null if not to use at all.
    onetimeToken: false, // default
);

You may wish to exclude a set of URIs from CSRF protection:

$request = $request->withAttribute(
    VerifyCsrfToken::EXCLUDE_URIS_KEY,
    [
        'http://example.com/foo/bar',
    ]
);

Form Tokenizer Methods

tokenizer

use Tobento\Service\Form\Form;
use Tobento\Service\Form\TokenizerInterface;

$form = new Form(
    tokenizer: $tokenizer,
);

var_dump($form->tokenizer() instanceof TokenizerInterface);
// bool(true)

generateToken

use Tobento\Service\Form\Form;

$form = new Form(
    tokenizer: $tokenizer,
);

var_dump($form->generateToken());
// string(40) "token string..."

generateTokenInput

use Tobento\Service\Form\Form;

$form = new Form(
    tokenizer: $tokenizer,
);

echo $form->generateTokenInput();
// <input name="_token" type="hidden" value="token string...">

Messages

Messages are used to display any message type on the form elements.

Check out the Message Service to learn more about it in general.

use Tobento\Service\Message\MessagesInterface;
use Tobento\Service\Message\Messages;

$messages = new Messages();

var_dump($messages instanceof MessagesInterface);
// bool(true)

Form Messages Methods

messages

use Tobento\Service\Form\Form;
use Tobento\Service\Message\MessagesInterface;

$form = new Form();

var_dump($form->messages() instanceof MessagesInterface);
// bool(true)

withMessages

Returns a new instance with the specified messages.

use Tobento\Service\Form\Form;
use Tobento\Service\Message\MessagesInterface;

$form = new Form();

$form = $form->withMessages(
    input: null, // null|MessagesInterface
);

getMessage

Returns the message for the specified key.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->getMessage('key'));
// string(0) ""

getRenderedMessageKeys

Returns the rendered message keys.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->getRenderedMessageKeys());
// array(0) { }

Adding Messages

An example of how to add messages for a specific form element.

use Tobento\Service\Form\Form;

$form = new Form();

$form->messages()->add(
    level: 'error',
    message: 'Please accept our terms.',
    key: 'terms',
);

<?= $form->input('terms', 'checkbox', 'terms') ?>
// <span class="form-message error">Please accept our terms.</span>
// <input name="terms" id="terms" type="checkbox">

You may check out the Validation Service for validating the form and passing the validator messages to the form.

CSRF Protection

If you have specified a tokenizer on the Form class, the form method will automatically add a hidden input element with the token.

<?= $form->form() ?>
// <form method="POST">
// <input name="_token" type="hidden" value="generated-token-string">

Method Spoofing

If you set PUT, PATCH or DELETE as the method, a hidden input element named _method will be automatically added for spoofing.

<?= $form->form(['method' => 'PUT']) ?>
// <form method="POST">
// <input name="_method" type="hidden" value="PUT">

Active Form Elements

getActiveElements

Returns the active elements.

use Tobento\Service\Form\Form;
use Tobento\Service\Form\ActiveElements;
use Tobento\Service\Form\ActiveElementsInterface;

$form = new Form(
    activeElements: new ActiveElements(),
);

var_dump($form->getActiveElements() instanceof ActiveElementsInterface);
// bool(true)

isActive

If the element is active.

use Tobento\Service\Form\Form;

$form = new Form();

$isActive = $form->isActive(
    name: 'colors',
    value: 'red', // the value to be active.
    default: null,
);

Form Helper Methods

nameToArray

Generates the specified name to an array string if name contains notation syntax.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->nameToArray('user.firstname'));
// string(15) "user[firstname]"

nameToNotation

Generates the specified name from an array string to a notation based name.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->nameToNotation('user[firstname]'));
// string(14) "user.firstname"

nameToId

Generates the specified name to a valid id.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->nameToId('user[firstname]'));
// string(14) "user_firstname"

hasArrayNotation

Check if the specified name contains a notation.

use Tobento\Service\Form\Form;

$form = new Form();

var_dump($form->hasArrayNotation('user.firstname'));
// bool(true)

Credits