Capture, validation and transfer of data
Generally speaking, the processing of the form can be divided into three steps:
- Input capture
That is, the capture of input data. Most often from POST or GET, but as we will show later, this is not a requirement. Any data of any origin can be "captured". - Validation
Of course, the captured data must be validated according to defined rules and/or using various validators. Without this, the data cannot be "released". - Transmission of data
With the data that we have captured and we know it's okay, we certainly want to do something about it. This is actually the final step of working with the form and also a separate topic.
Input capture
The catchInput method is used to catch the input data and returns true/false depending on whether the form was submitted or not.
if( $form->catchInput() ) {
//Form has been sent
//... ... ...
}
As we said in the definition chapter, each form has a name that is sent in a hidden form field and by this it can be identified that this form was sent even though there may be more forms on the page.
However, we also realized that this is not the only way to capture data. For example, if we need a form to capture and validate data within a REST API, we're hardly going to ask the API user to send some "magic value". That's why we can force form capture even without the existence of a special field:
$form->catchInput( force_catch: true ) );
Thus, the form is captured even without the form name field. However, it still captures data from POST or GET (depending on how it is defined).
However, we have already said that the form does not need to capture data from POST or GET, but any data from anywhere. This can be solved as follows:
$form->catchInput(
input_data: $my_data,
force_catch: true
);
The data must be either an array or an instance of Jet\Data_Array.
Validation
In order to clarify validation, we need to go into the topic of form definitions. Many field types have basic validation built in. For example, the numbered Form_Field_Int and Form_Field_Float fields have the ability to limit the range of numbers entered, the email Form_Field_Email field validates the address automatically, the general Form_Field_Input field allows you to enter a regular expression to check the format, and so on. See form field types.
However, this integrated validation may not be enough. Imagine you are creating a registration form and you need to find out if the user with the given email is already registered. Or whether the password is strong enough. There are many possibilities. That's why Jet has the ability to define validators for fields.
But before we get to validators, we need to look at defining error codes. And that's where we start.
Error codes
If you go through the form field types, you'll notice that the vast majority of fields have some sort of default error codes. These codes represent error conditions during validation depending on the field type. Jet will even require you to define an error message for the relevant error code otherwise the form will throw an exception. What does this mean? Let's see it in practice:
use Jet\Form;
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$reg_form = new Form('registration_form', [
$username_field
]);
We have a registration form with a username field. This field is marked as required ($username_field->setIsRequired( true )), but no error message is defined at all. Thus, the form is invalid and Jet throws an exception when you try to use it. In order for everything to work properly, you need to define an error message for the correct error code:
use Jet\Form;
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
Form_Field_Input::ERROR_CODE_EMPTY => 'Please enter your username'
]);
$reg_form = new Form('registration_form', [
$username_field
]);
And now it's right. The field is now automatically checked as mandatory, and the user may receive an error message (or an error message may be generated in the REST API response). Jet helps you create a consistent application.
Note: Please note that the error message on definition is not "thrown" through the compiler. Forms are automatically linked to the compiler - see a separate section.
Of course, the default error codes are not the final list of error codes that the form can handle. You can (or actually must) define additional error codes according to your needs. For example, when creating your own validator.
Validators
We will smoothly build on and improve our embryonic registration form. We already have a check if the user has entered the data at all. This will be checked from now on. But how to check if someone else already has the username? For that we will already need our own validator:
use Jet\Form;
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
Form_Field_Input::ERROR_CODE_EMPTY => 'Please enter your username',
'already_exists' => 'Sorry, but username %username% is already reserved'
]);
$username_field->setValidator( function( Form_Field_Input $field ) : bool {
$username = $field->getValue();
if(Auth_Visitor_User::usernameExists($username)) {
$field->setError(
code: 'already_exists',
data: ['username'=>$username]
);
return false;
}
return true;
} );
$reg_form = new Form('registration_form', [
$username_field
]);
A validator is nothing more than a callback that takes as its only parameter an instance of the field it is supposed to validate. Its job is to return true if it hasn't encountered a problem, and in case of an error set the appropriate error code and return false.
You will have noticed that it is possible to pass error data along with the error code, which can be added directly to the appropriate places in the error message (which is translated, of course).
Form validation result
We have shown how to hang validation on individual fields. Now we will show how to work with validation at the form level. Calling validation is simple:
if(
$form->catchInput() &&
$form->validate()
) {
//Form has been sent and values are valid
//... ... ...
}
This was a basic common situation where the form is captured by default, validated, and if everything is OK, something will happen. Any error messages are automatically displayed for the form.
But what if we just need a bug list if the validation fails? Here's how we do it:
if( $form->catchInput() ) {
if($form->validate()) {
//Form has been sent and values are valid
//... ... ...
} else {
$errors = $form->getValidationErrors();
var_dump($errors);
//... ... ...
if(!$username_field->isValid()) {
var_dump(
$username_field->getLastErrorCode(),
$username_field->getLastErrorCode()
);
}
}
}
So it is possible to continue to operate with errors. Either with all of them, or individually, on individual fields.
Influencing validation
Imagine this situation: we have an e-shop order form. It contains a checkbox "purchase to company". If this is checked, we need to start validating the company data. Otherwise, the data is not required. How to do it? Influence the form after it has been captured, before validation:
if( $form->catchInput() ) {
if($form->field('is_company_order')->getValue()) {
$form->field('company_name')->setIsRequired( true );
$form->field('company_id')->setIsRequired( true );
$form->field('company_vat_id')->setIsRequired( true );
}
if($form->validate()) {
//Form has been sent and values are valid
//... ... ...
}
}
Data transfare
The form is already captured, validated and it is possible to operate with the captured data.
Simply obtaining values
The first basic option (but one that I use minimally in practice) is to take all form values as an array:
if(
$form->catchInput() &&
$form->validate()
) {
var_dump( $form->getValues() );
}
Value catchers
I use value catchers much more often. Then using the form looks like this:
if(
$form->catchInput() &&
$form->validate()
) {
$form->catchFieldValues();
//Form has been sent, is valid ....
}
Or even more commonly (as seen in the sample application) as follows:
if( $form->catch() ) {
//Form has been sent, is valid ....
}
And that's it! The data magically goes where I need it to go and that's it. All right, well... I'm just kidding. Yes, this is a common way to use forms, and automatically generated forms mapped to classes (most commonly from DataModel and configuration system) are already ready for this style of use.
But of course we'll talk about why and how it works. Few forms will be lonely. Most forms will be related to some object. For example, a registration form will relate to a user - that is, some instance of a class representing the user. Or an order form in an e-shop will refer to an order object, a form for editing a product description will refer to a product object, this form in which I am currently writing text will refer to a documentation article object, and so on.
What do we need to happen after the form is captured and we know it's valid? We need to "translate" the data from the form to the object that the form refers to. That is, when I save this text, this text reaches the property of the class representing the article, and this class is then saved (it is, of course, DataModel).
As has already been said, if you use what Jet can do, you only have to worry about it if it's a special situation. Otherwise, this mapping happens automatically. But it's good, or actually necessary, to know how it works. Let's try to imagine a situation where we are doing user registration, but the user will not be stored in some database by default (i.e., you don't use DataModel), but maybe sent to some service using some API.
What was said at the beginning still applies. A user is an object - an instance of a class. However, within this class we do everything manually, so to speak:
use Jet\Form;
use Jet\Form_Field_Input;
class MyUser {
protected string $username = '';
protected ?Form $reg_form = null;
public function getUsername(): string
{
return $this->username;
}
public function setUsername( string $username ): void
{
$this->username = $username;
}
public function getRegForm() : Form
{
if(!$this->reg_form) {
$username_field = new Form_Field_Input(
name: 'username',
label: 'Username'
);
$username_field->setDefaultValue( $this->username );
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
//... ... ..
]);
$username_field->setValidator(function( Form_Field_Input $field ) : bool {
//... ... ...
});
//************************************
//************************************
$username_field->setFieldValueCatcher(function( $value ) {
$this->setUsername($value);
});
//************************************
//************************************
$this->reg_form = new Form('reg_form', [
$username_field
]);
}
return $this->reg_form;
}
public function save() : void
{
//TODO: ... ...
}
}
The class and the form are ready. Now you just need to use it in some controller:
$new_user = new MyUser();
$form = $new_user->getRegForm();
if($form->catch()) {
$new_user->save();
}
Attention! I may not please you now, but what we have just shown you is unnecessary. In practice, such a situation can be handled differently by mapping classes to forms. But in any case, knowing this principle is important, and in some situations it is necessary to use it. I mean, it's not that unnecessary :-) ;-).
Where next?
Now you can define, capture, validate and use the form. What next? It's time to look at how to display / render forms.