From LifeType Wiki
Contents |
Forms and data validation
In pLog 0.3.2 and previous version, the Action::validate() method was overriden by child classes and used to implement data validation in forms or incoming requests. However, that method proved to be painful and repetitive and a new scheme for data validation was developed.
The new framework relies on the FormValidator class for the validation logic and on the xxxValidator classes for the actual checking of the data. Every Action class has its own private FormValidator object in Action::_form and it can be used to validate both data coming from either POST or GET requests.
The result is that it is no longer necessary to reimplement the Action::validate() method unless we need to provide some kind of non-standard validation that cannot be covered by using the default Validator clases or by creating our own one.
FormValidator
FormValidator is a much more flexible system that will save us from creating too much redundant code while at the same being powerful. It works on the principle that before a form can be processed, we need to inform the form processor of which fields are going to be in the request and which format they should have. We can also let the form processor know about which fields will be there but do not need to be processed, so that they can be copied back to the form in case it needs to be shown again.
Validating fields
In order to perform some kind of check on a form field, we must use the Action::registerFieldValidator(), which is an alias to FormValidator::registerFieldValidator($fieldName,$validator,$onlyIfAvailable=false) This method will inform the form processor that it should expect a field called $fieldName and that it should be validated using the $validator class. The third parameter is an optional one that tells the form processor not to try to process the value of the field if it is not available (not avaiable in the sense of being empty) This is useful in cases we need to validate optional data.
It is advisable to register your validators in the constructor of your Action class. It is not a bad practice to still use Action::validate() for this but please keep in mind that you should call parent::validate() or else no processing will take place.
The following code is preferable:
class myActionClass extends Action
{
function myActionClass(...)
{
$this->Action(...);
$this->registerFieldValidator( "myField", new IntegerValidator());
...
}
}
instead of the following code (just to avoid stupid mistakes):
class myActionClass extends Action
{
function myActionClass(...)
{
$this->Action(...);
}
function validate()
{
$this->registerFieldValidator( "myField", new IntegerValidator());
...
return( parent::validate());
}
}
There is a list with all the validator classes available below.
Registering fields
If an error occurs, the form data which the form processor is aware of will be copied back to the view. But what happens to the rest of the data from our form? Will it be lost?
In order to make the form processor "remember" some data without validating it so that it can be put back in our form, we should use the method Action::registerField() which is an alias to FormValidator::registerField($field). This method will make the form processor store the value of the given field but it will not run any data checks on it.
Validator classes
Data validation classes in LifeType extend the Validator base class and are made up of Rules. A Rule is a simple condition that is implemented by extending the Rule base class. Each Validator can be made up of one or more rules. So for example we could have a NumericRangeValidator that would be made up of the following rules:
- NumericRule in order to check that the value is a number.
- RangeRule in order to check that the value is within the given range.
The idea behind rules is that they can be reused in more than one Validator class.
Each validator should have at least one rule but there is no upper limit as to how many it can have.
Validators can be found under class/data/validator/ while rules can be foud under class/data/validator/rules but this is a brief list with all the most commonly used validators available in 1.0:
- IntegerValidator: returns valid if the given data is an integer.
- StringValidator: returns valid if the given data is a string or not valid if not or if it is empty.
- ArrayValidator: returns valid if the given data is an array.
- EmailValidator: returns valid if the given email address is valid. It performs some basic checks via a regular expressions to make sure that the address has the basic format.
- PasswordValidator: return valid if the given password is a valid password. At the moment it only performs a check on the minimum length of the given string. The minimum length is fetched from the global configuration, via the key password_minimum_length.
- HttpUrlValidator: checks whether the given data represents a valid URL.
- EmptyValidator: dummy validator that always returns true.
- UsernameValidator: checks whether a username is not in the list of forbidden usernames. The list of forbidden usernames is gathered from the global configuration via the forbidden_usernames key.
- ChainedValidator: Allows to chain several validators one after each other to get even more complex conditions. It takes one parameter in the constructor, an array with all the Validator objects that we would like to use. It will start processing the data through the first validator and stop as soon as one of them returns false.
$validator = new ChainedValidator( Array( new StringValidator(), new HttpUrlValidator()));
Implementing custom validators
In order to implement your own Validator, you only need to implement the Validator::validate($value) method and return true if the data is valid or false if it is not. Usually validators take no parameters in the constructor.
In order to add rules to the validator, the Validator::addRule(&$rule) must be used. However, we can also chain validator objects via Validator::addValidator(&$rule).
When creating our class, we must remember to call the parent::validate() method so that other tasks can be performed.
Validating our data
All we need to do to get our data validated is to register the fields we'd like to be validated and let the Controller execute our action. The Action::validate() method will take care of everything else.
What to do when validation errors occur
If a validation error occurs during the process, we are given the chance show an error view that would display again the form values so that users can correct the incorrect ones. Optionally, we can also display error messages both on the top of our form and about which particular field caused the error.
In order to show a custom view with a custom error message when a validation error occurs, we have to use Action::setValidationErrorView($view) and View::setErrorMessage(). By default, a validation error will enable the flag that tells that a view was generated via an error.
We can use any view to as the error view as long as it extends the main View class.
This is a simple example of how to set a valid view that will show an error message:
class myActionClass extends Action
{
function myActionClass(...)
{
$this->Action(...);
$this->registerFieldValidator( "myField", new IntegerValidator());
...
$view = new myActionClassView();
$view->setErrorMessage( "This error message will be displayed if there are data errors!" );
$this->setValidationErrorView( $view );
}
}
Here we define which view and which message should be shown in case there are validation errors. Please see the section "How to report errors" in order to have an idea of how this actually happens in the view, specially if we are using our own custom template.
Again, the good thing about the data validation framework is that all this happens automatically and we do not need to worry about implementing endless if blocks in the validate() method as it was before.
Checking the form
The FormValidator class provides several methods to check on its status, if it has actually run yet and if it has, what the outcome of the processing was. These methods are hardly used from within PHP code and as we will see later on, they are mostly used at the template level to check whether errors have happened and to display the right message.
The most useful methods are:
- formHasRun(): returns true if the form has already run or false if it has. A form has "run" whenever its validate($request) method has been executed and therefore, this method will always return true if called from within the constructor of our action class. Please keep in mind that data validation is done in Action::validate() and not in the constructor. A more suitable place to check whether a form has been processed is in the Action::perform(), if needed.
- formIsValid(): returns true if there wasn't any validation error. If the form has not run, this method will return false so please make sure to call formHasRun() before checking whether the form is valid or not.
Checking the fields
In order to check whether a field was correctly validated, we should use the FormValidator::isFieldValid($field). This method will return true if the field was successfully validated or false otherwise.
Forcing (or cancelling) validation errors
Sometimes additional checks are needed that cannot be performed via validators (or it would be too complex or cumbersome), but we would still like to force certain fields of a form not to be valid even though they had the right format.
One real situation is a form where blog administrators can create new users. The "userName" field is validated agains a UsernameValidator and agains a StringValidator but later on we will still check whether the username is already taken or not. If it is, then we should return back to the original view, show the form and inform the user about the situation.
The FormValidator class offers a method that allows to change the validation status of a field (we can also change a field status from negatively validated to positively validated). The method is FormValidator::setFieldValidationStatus($fieldName,$status), where the first parameter is the field name and the second is a boolean value indicating its status.
Needless to say, this method should be called after the form has been processed or else its processing will overwrite any previously set value.
Data validation triggers
Sometimes we would like to perform some actions in case a validation error happens. In that case, we can overwrite the Action::validationErrorProcessing() and put there our own validation error logic. At this point the error has already happened so we cannot change the status of the form, but we can change the status of any of the fields, the error message, or select a different view as the processing of these things happens just after this method is executed.
However, do not forget to call parent::validationErrorProcessing() after you've finished with your logic as the Action class uses this method for its own purposes.
How to report errors
In Validating our data we have learned how to check our data and the outcome of the form processor but we still need to tie this with the view and the underlying template which in the end, is the part which creates content for the user/browser.
Showing an error message
In order to show an error message, there are two ways to do it:
- We can check whether the view is a view reporting an error message and act accordingly.
- We can check the $form object (the FormValidator that was used in the Action class) and use the FormValidator::isFormValid() and FormValidator::hasFormRun() methods.
Views showing error messages
Since validation errors will trigger View::setErrorMessage($message), a view with a validation error becomes automatically an error view. The way to check whether a view is an error view and display the message that was set as the $message parameter is to use the $viewIsError and $viewErrorMessage Smarty variables. These variables are always available in the templates:
{if $viewIsError}
<div class="FormError">
{$viewErrorMessage}
</div>
{/if}
One good example of this implementation is templates/admin/errormessage.template. This file is included by several other template files throughout the templates/admin/ folder and it is also available for our own custom templates to use (in fact, it is the preferred way)
Checking the $form object
Alternatively, we can use the FormValidator::isFormValid() and FormValidator::hasFormRun() methods:
{if $form->formHasRun()}
{if !$form->formIsValid()}
{$message}
{/if}
{/if}
In this case, no default error message can be provided from the PHP code so we will have to provide our own from within the Smarty code. A good practice is to put the code above in its own file and then from our templates, include it like this:
{include file="formvalidator.template" message="This is my validation error message"}
The file templates/admin/formvalidator.template is a good example of this and it is used in many places throughout LifeType. If needed, it can also be used in your custom templates.
Getting our data back in place
In our case, the Action::validationErrorProcessing() method will take care of copying all the fields back to the view so that they can be displayed again in our template. The values will be exported with the same name as the field so our forms should look like:
<input type="text" name="myTextField" value="{$myTextField}" />
...
<select name="mySelectField">
<option value="1" {if $mySelectField==1} selected="selected" {/if}>Value number 1</option>
<option value="2" {if $mySelectField==2} selected="selected" {/if}>Value number 2</option>
<option value="3" {if $mySelectField==3} selected="selected" {/if}>Value number 3</option>
...
</select>
If the form has not been processed (like the first time when we show the view), all the field variables will be empty.
In case we are forcing some fields to be valid, we need to call the Action::setCommonData($copyData=false) method by setting its only parameter to true and therefore forcing it to copy the field values from the form validator to the view layer, as it does not happen automatically.