It is strongly recommended to validate any request data. All request data including headers and cookies comes from the user and it's content is dictated by the the user. This user may as well be a malicious user trying to attack your application/server/database or use your application to attack other people visiting your website through XSS-attacks or similar. No input to your application which is not under your control should therefore be trusted.
There are other reasons to validate data: Think of a newsletter application form - it's always a good thing to check whether the data the user entered is at least a syntactically correct email-address. Sometimes typing errors happen and the user is frustrated that he didn't get the newsletter he ordered. It would have been better to check the data the user entered and provide him with a friendly error message at the time of registration.
The best situation you can be in is if you know exactly what input data is valid - e.g. if your page expects an id parameter to pull a record from the database you know for sure that it must absolutely be an integer and everything else is an attempt to trick your application to do something wrong. Other situations may not be as easy, but you should still try to validate as much data as possible.
Validation happens if the action serves the incoming request
method, that is if the action declares an execute method that matches
the request type (executeRead() for get
request, executeWrite() for post request or a
generic execute() method), immediatly before
the action is executed.
The rules for the validation are defined in validators. A validator is an object that checks wether an input parameter conforms to a set of given constraints - such as "is the parameter 'id' really an integer value etc.". There two ways of registering a validator and a third way to perform more elaborate validations that cannot be handled by a validator:
There are several ways of validating input data. The simplest way is placing an XML configuration file with the proper configuration in the "validate" directory of the module. The XML file must be named <ActionName>.xml, that is if your action is named "LoginAction", the XML file must be "Login.xml". In this file, you can define which validators will be registered for any request handled by the action. It is important to note that even though you can set some validators not to execute on a read or write request, those validators will be registered anyways and as you can't register a validator twice, you can only have one input parameter validated in a different manner on read and write by defining two different validators. Another way which provides finer control over what validators will be registered on read/write requests is the registerValidators() family of methods.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<configurations>
<!-- custom validators and default parameters -->
<configuration>
<validators>
<validator class="string" name="username_too_short">
<argument>username</argument>
<error>The username you supplied is less than 4 characters</error>
<parameter name="min">5</parameter>
</validator>
</validators>
</configuration>
<configurations>This registers a single validator that checks wether the
username is longer than 5 characters. Let's look at this snippet
in detail. The interestion block is the
<validators> element. It contains a list of
<validator> elements, each one representing a
validator. A validator must have a type ("string" in this case,
alternatively you can use the full class name). The name is
optional and must be unique ("username_too_short" here) when
supplied . Some validators can act upon multiple input parameters,
each one wrapped in a <argument> tag. You can
also validate array input parameters using a special
syntax. You can define different <error>
blocks for the various validation errors by supplying a name
attribute, in this case there is only the generic error available.
Finally, the <parameters> block lists the
parameters that get passed into the validator. These depend on the
type of validator you're trying to register. The
AgaviStringValidator being used here for example accepts
parameters such as minimum and maximum length etc.
While the XML configuration method is convenient most of the
time, sometimes a more fine grained control is nessesary. Sometimes
you might wish to validate a parameter differently on a read request
than on a write request or the validation might depend on data no
known when you write the application. Think of a form that gets
generated depending on some config parameters pulled from a
database. When you write the action, you don't know yet what form
fields will be available and which type they have - however when the
form gets displayed you will know. You just need to tell agavi about
it - this is where the registerValidators()
methods come into play. If your action defines a method
registerReadValidators(), it will be
executed before executeRead() and all
validators that you registered in this method will be executed as
well. The same is true for
registerWriteValidators(), only that it
will be called before executeWrite() will
run and registerValidators() will be
executed before any of the execute methods will be run. That way,
you can dynamically add validators, you can even mix both methods:
use the XML for any validators that you you'll need for shure and
use execute*Validators() to add anything that is dynamic. However,
as noted above you can't register a validator twice.
public function registerWriteValidators()
{
$validationManager = $this->getContext()->getValidationManager();
$arguments = array('username');
$errors = array('' => 'The username you supplied is less than 4 characters.');
$parameters = array (
'name' => 'username_too_short',
'min' => '5',
'severity' => 'error',
'method' => NULL,
'required' => true,
'class' => 'string'
);
$validator = new AgaviStringValidator();
$validator->initialize($this->getContext(), $parameters, $arguments, $errors);
$validationManager->addChild($validator);
}This example does the same as the XML style registration above.
If an action declares a method with the name "validate()", it will get called after the registered validators have run but before the execute Method gets called. It recieves a single parameter, $parameters with the type AgaviRequestDataHolder. If validate returns true, the execution continues, if it returns false, validation fails and the error must be handled. As with the register*Validator() family of methods, a method named validateRead(AgaviRequestDataHolder $rd) will only be called in read requests and a method named validateWrite(AgaviRequestDataHolder $rd) only on write request. You can use these method to perform advanced validation that is not possible with validators. As validators are generic and reusable, you should look into creating your own validator whenever feasible.
Once validation failed, the error must be handled. The default way is to render an error view, but sometimes you might wish to slip in some logic such as logging the error. You can do this by declaring a method named "handleError()" in your action. You can also define different handlers for the request methods using handle*Error() (handleReadError, handleWriteError and so on). It will be called if a validation error occured and recieves a single parameter, $parameters with the type AgaviRequestDataHolder. It must return the name of a view, just as the execute methods do.
Once you validated the input data, there may be errors that must be returned to the client. The standard way is displaying the form back to the user and having him correct the errors in the input data. This can be done by pulling the supplied data from the request object in your form template and filling the input fields with the proper value. There is a more convenient way though: The FormPopulationFilter. It is a regular filter that gets executed everytime you post a form by default. It parses the resulting document and extracts the form element whose action attribute matches the current page's URL. It then matches the input fields in this form against the request parameters and fills in the request values in the proper input fields. It even marks the input field and the corresponding label (if any) with a special CSS class that you can use to indicate an error to your users. While inserting the request parameters, it takes care of escaping the values properly so that XSS attacks are not possible.
If you have an internationalized application you need to translate the error messages as well. To make this possible, each validator accepts a "translation_domain" parameter. All error messages will be translated with the Translator registered for this domain.
Depending on the validator type, every validator accepts different parameters.