tobento / service-form
Building HTML forms easily.
Requires
- php: >=8.0
- tobento/service-collection: ^1.0
- tobento/service-message: ^1.0
Requires (Dev)
- nyholm/psr7: ^1.4
- nyholm/psr7-server: ^1.0
- phpunit/phpunit: ^9.5
- psr/http-message: ^1.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- tobento/service-container: ^1.0
- tobento/service-middleware: ^1.0
- tobento/service-requester: ^1.0
- tobento/service-responser: ^1.0
- tobento/service-session: ^1.0
- vimeo/psalm: ^4.0
Suggests
- psr/http-message: Required for using middleware
- psr/http-server-handler: Required for using middleware
- psr/http-server-middleware: Required for using middleware
- tobento/service-requester: Required for using middleware
- tobento/service-responser: Required for using ResponserFormFactory
- tobento/service-session: Required for using session tokenizer
README
Buliding HTML forms.
Table of Contents
- Getting started
- Documentation
- Credits
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)