Forms Used Standalone

Nette Forms greatly facilitates the creation and processing of web forms in your applications. In this chapter, you will learn how to use forms alone without using presenters.

However, if you use presenters and Nette Application, there is a guide for you: forms in presenters.

Installation:

composer require nette/forms

First Form

We will try to write a simple registration form. Its code will look like this:

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

And let's render it:

$form->render();

and the result should look like this:

The form is an object of the Nette\Forms\Form class (the Nette\Application\UI\Form class is used in presenters). We added the controls name, password and sending button to it.

Now we will revive the form. By asking $form->isSuccess(), we will find out whether the form was submitted and whether it was filled in validly. If so, we will dump the data. After the definition of the form we will add:

if ($form->isSuccess()) {
	echo 'The form has been filled in and submitted correctly';
	$data = $form->getValues();
	// $data->name contains name
	// $data->password contains password
	var_dump($data);
}

Method getValues($asArray = false) returns the sent data in the form of an object ArrayHash. If you prefer to get the data as an array, use getValues(true). The variable $data contains keys name and password with data entered by the user.

Usually we send the data directly for further processing, which can be, for example, insertion into the database. However, an error may occur during processing, for example, the username is already taken. In this case, we pass the error back to the form using addError() and let it redrawn, with an error message:

$form->addError('Sorry, username is already in use.');

After processing the form, we will redirect to the next page. This prevents the form from being unintentionally resubmitted by clicking the refresh, back button, or moving the browser history.

By default, the form is sent using the POST method to the same page. Both can be changed:

$form->setAction('/submit.php');
$form->setMethod('GET');

And that is all :-) We have a functional and perfectly secured form.

Try adding more form controls.

Access to Controls

The form and its individual controls are called components. They create a component tree, where the root is the form. You can access the individual controls as follows:

$input = $form->getComponent('name');
// alternative syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternative syntax: $button = $form['send'];

Controls are removed using unset:

unset($form['name']);

Validation Rules

The word valid was used here, but the form has no validation rules yet. Let's fix it.

The name will be mandatory, so we will mark it with the method setRequired(), whose argument is the text of the error message that will be displayed if the user does not fill it. If no argument is given, the default error message is used.

$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');

Try to submit the form without the name filled in and you will see that an error message is displayed and the browser or server will reject it until you fill it.

At the same time, you will not be able cheat the system by typing only spaces in the input, for example. No way. Nette automatically trims left and right whitespace. Try it. It's something you should always do with every single-line input, but it's often forgotten. Nette does it automatically. (You can try to fool the forms and send a multiline string as the name. Even here, Nette won't be fooled and the line breaks will change to spaces.)

The form is always validated on the server side, but JavaScript validation is also generated, which is quick and the user knows of the error immediately, without having to send the form to the server. This is handled by the script netteForms.js. Add it to the page:

<script src="https://nette.github.io/resources/js/2/netteForms.min.js"></script>

If you look in the source code of the page with form, you may notice that Nette inserts the required fields into elements with a CSS class required. Try adding the following style to the template, and the “Name” label will be red. Elegantly, we mark the required fields for the users:

<style>
.required label { color: maroon }
</style>

Additional validation rules will be added by method addRule(). The first parameter is rule, the second is again the text of the error message, and the optional validation rule argument can follow. What does that mean?

The form will get another optional input age with the condition, that it has to be a number (addInteger()) and in certain boundaries ($form::RANGE). And here we will use the third argument of addRule(), the range itself:

$form->addInteger('age', 'Age:')
	->setRequired(false)
	->addRule($form::RANGE, 'You must be older 18 years and be under 120.', [18, 120]);

If the user does not fill in the field, the validation rules will not be verified, because the field is optional.

Obviously room for a small refactoring is available. In the error message and in the third parameter, the numbers are listed in duplicate, which is not ideal. If we were creating a multilingual form and the message containing numbers would have to be translated into multiple languages, it would make it more difficult to change values. For this reason, substitute characters %d can be used:

	->addRule($form::RANGE, 'You must be older %d years and be under %d.', [18, 120]);

Let's return to the password field, make it required, and verify the minimum password length ($form::MIN_LENGTH), again using the substitute characters in the message:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MIN_LENGTH, 'Your password has to be at least %d long', 8);

We will add a field passwordVerify to the form, where the user enters the password again, for checking. Using validation rules, we check whether both passwords are the same ($form::EQUAL). And as an argument we give a reference to the first password using square brackets:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::EQUAL, 'Password mismatch', $form['password'])
	->setOmitted();

Using setOmitted(), we marked an element whose value we don't really care about and which exists only for validation. Its value is not passed to $data.

We have a fully functional form with validation in PHP and JavaScript. Nette's validation capabilities are much broader, you can create conditions, display and hide parts of a page according to them, etc. You can find out everything in the chapter on form validation.

Default Values

We often set default values for form controls:

$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);

It is often useful to set default values for all controls at once. For example, when the form is used to edit records. We read the record from the database and set it as default values:

//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

Call setDefaults() after defining the controls.

Rendering the Form

By default, the form is rendered as a table. The individual controls follows basic web accessibility guidelines. All labels are generated as <label> elements and are associated with their inputs, clicking on the label moves the cursor on the input.

We can set any HTML attributes for each element. For example, add a placeholder:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

There are really a lot of ways to render a form, so it's dedicated chapter about rendering.

Multiple Submit Buttons

If the form has more than one button, we usually need to distinguish which one was pressed. The method isSubmittedBy() of the button returns this information to us:

$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

Do not omit the $form->isSuccess() to verify the validity of the data.

When a form is submitted with the Enter key, it is treated as if it had been submitted with the first button.

Vulnerability Protection

Nette Framework puts a great effort to be safe and since forms are the most common user input, Nette forms are as good as impenetrable.

In addition to protecting the forms against attack well-known vulnerabilities such as Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) it does a lot of small security tasks that you no longer have to think about.

For example, it filters out all control characters from the inputs and checks the validity of the UTF-8 encoding, so that the data from the form will always be clean. For select boxes and radio lists, it verifies that the selected items were actually from the offered ones and there was no forgery. We've already mentioned that for single-line text input, it removes end-of-line characters that an attacker could send there. For multiline inputs, it normalizes the end-of-line characters. And so on.

Nette fixes security vulnerabilities for you that most programmers have no idea exist.

The mentioned CSRF attack is that an attacker lures the victim to visit a page that silently executes a request in the victim's browser to the server where the victim is currently logged in, and the server believes that the request was made by the victim at will. Therefore, Nette prevents the form from being submitted via POST from another domain. You can easily turn on the defense against this attack:

$form->addProtection();

It's strongly recommended to apply this protection to the forms in an administrative part of your application which changes sensitive data. The framework protects against a CSRF attack by generating and validating authentication token that is stored in a session (the argument is the error message shown if the token has expired). That's why it is necessary to have an session started before displaying the form. In the administration part of the website, the session is usually already started, due to the user's login. Otherwise, start the session with the method Nette\Http\Session::start().

So, we have a quick introduction to forms in Nette. Try looking in the examples directory in the distribution for more inspiration.

version: 4.0 3.x 2.x