On input validation 2
by Naneau
I posted about it yesterday. I’ve thought about it some more. Wrote some code. And I’m happy! I can validate input without fuss again. Yay. I really did get tired from writing trivial form validation rules into my controllers time and time again. Allow me to demonstrate my new approach.
First, I think about what fields I need, and what kind of validation they require. I subclass my validator class ‘Naneau_Validator_Abstract’, which does little more than apply validators to fields and store error messages. In the constructor I add Zend_Validate objects to fields:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php require_once 'Naneau/Validator/Abstract.php'; require_once 'Zend/Validate/StringLength.php'; require_once 'Zend/Validate/Regex.php'; require_once 'Zend/Validate/Alnum.php'; require_once 'Zend/Validate/Digits.php'; class Naneau_Validator_Demo extends Naneau_Validator_Abstract { public function __construct() { $this->setRequired('name', 'Name is a required field'); $this->addFieldValidator('name', new Zend_Validate_StringLength(1,150), 'Name must be between 1 and 5 characters long, yes, 5!'); //name $this->addFieldValidator('numeric', new Zend_Validate_Digits(), 'Numeric must be... numeric.'); //alnum $this->setRequired('alnum', 'Alnum is a required field'); $this->addFieldValidator('alnum', new Zend_Validate_Alnum(), 'Alnum must be alpha-numeric, d\'oh!'); //alnum $this->setRequired('url', 'Url is a required field'); $this->addFieldValidator('url', new Zend_Validate_Regex('/^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\//i'), 'Url has to be valid, try adding a trailing "/"'); //url } } |
This will give me a nice validator class, that I can use in my controllers like:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function someAction() { Zend_Loader::loadClass('Naneau_Validator_Demo'); $validator = new Naneau_Validator_Demo(); //the validator (in this case, for 'demo') if (!empty($_POST) && $validator->validateArray($_POST)) { //save whatever it is I wanted to validate //redirect } //display a form //assign error messages to the view } |
This pattern is very reusable, and I think I can even go as far as to automate it. Right now it’s based on Zend Framework classes. But the same principle works for other frameworks as well. The concept of writing a validator, that is decoupled from the form it will be used to validate, stays the same.
But wait, there’s more! If you have a standard validator that can validate an array of values, why not do it using ajax calls. See this little demo for how handy it can be. It uses the same validator class, and just a very basic controller to validate it. If you put all your validator classes in the same directory, it’s trivial to find one for each form you output, and add form validation unobtrusively and automatically. It means that you don’t have to write any validation rules in JavaScript. All you have to do is add an id attribute to the form element, which the controller could then match to a validator class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /** * What to put in front of validator classes * * @var string */ private $_prepend = 'Naneau_Validator_'; public function formAction() { if ($validator = $this->getValidator()) { $return = array( 'success' => $validator->validateArray($_POST), 'messages' => $validator->getMessages() ); $this->sendJsonResponse($return); } else { $this->sendJsonResponse(array('success' => true)); //no validation means... true! //yes, that doesn't make any sense :x } } /** * get validator out of request * @return Naneau_Validator_Abstract */ private function getValidator() { try { $validator = $this->_prepend . ucfirst($this->getRequest()->getParam('form')); Zend_Loader::loadClass($validator); return new $validator; } catch (Exception $e) { return false; } } |
Note that the original controller also has a function to validate a single field. But validating fields on each blur through an ajax call causes quite a bit of overhead
.
Anyway, there it is! Naneau Validator Abstract
Comments
Hi,
Nice idea
Btw, you got one problem on sample, i guess URI field doesn’t work.
I added: http://www.naneau.nl
And still keeps saying: Url has to be valid, try adding a trailing “/”
You really DO have to add a trailing slash! http://naneau.nl/ works just fine
. It’s my regular expression, it isn’t completely fool-proof. But it’s just a demo, so I’m going to stick with it for now.
Interestingly enough, though totally unrelated, my javascript external link checker thinks that http://www.naneau.nl/ isn’t the same thing as http://naneau.nl/ . I should fix that
Even though I’m opposed to adding the totally obsolete www part to my URIs if I can avoid it.
Hi,
nice page! I think you’re on a good way with this blog because you always post some nice hints how to work with the Zend Framework and other nice functions like the CSS Charts
This topic over here looks also very interesting because there are a lot of (older) solutions but they don’t fit in the framework. But can you be so kind to post the whole example sourcecode? (for example the Naneau_Validator_Abstract class).
Best regards,
dino
I’m working on a code browser for things like this, expect either that, or some kind of other download with this and other classes sometime soon…!
P.S. Thanks
Okay, then I hope “soon” is really soon and not just in a few weeks
I’ve put it up, see the end of the post. I must warn you though, I have not tested it thoroughly.
First of all, thank you. But I’m not able to work with this example, I don’t know exactly if it’s just my tiredness or something else…
So I will wait until you post the whole example with all files
I’m watching you
best regards
The validator class is just an example of how you might do input validation. You should subclass it to create something usable, see the first code example in the post on how you should do that. Using it is something different. I tried to make it broadly applicable, you could just validate $_POST, but there’s also a field-validating method. There are phpdoc-blocks for each method.
I found that I needed a concept of ‘required’ fields. Those fields get checked no matter what, and will raise an error if they are empty(). Non-required fields will only get checked if they have contents. See the demo to get an idea of how that works.
hey, so now I start^^
This is how i understood your way (the action names are copied from the example above)
someAction():
displays the form and includes http://naneau.nl/zf/js/application/formvalidator/forms.js
onSubmit() of the form:
ajax request to the url in the forms.js, the action should be formAction(), right?
This actions checks all values and validates them and send the json response
if the validation was successful, the javascript performs a page refresh to the someAction(); and validates it again (if the user doesn’t allow js or something else) and then it performs the form
Hope i understood you well, else correct me
Yes, that is the basic flow. I haven’t explained the javascript part in detail, but you did get the gist of it. I have a single controller (the url for which is in the forms.js file). The ajax request to that controller gets an argument which form it is (which the javascript gets out of the id of the form) and is therefore able to find a validator for it, and validate all fields.
If the form is valid it does a “real” submit of the form. Because you can’t rely on JavaScript validation, you have to validate it again on the server after that submit. In the demo application you just get redirected back to the demo form, but in a real world scenario you would probably want to redirect to something useful, and alert the user that his data has been saved.
The someAction could just be any normal controller action where you would have the need for a form.
Hey Naneau!
Thank you for your help on zftalk! I’ve gotten started and really love this approach.
I’ve worked to avoid the double submit issue and I just wanted to share my approach that seems to work:
* Javascript part in formvalidation.js*
submit: function(e) {
Event.stop(e);
//stop the submit
var test = $$(‘#’ + this.form.id);
var el = $$(‘#’ + this.form.id + ‘ input.submit’);
if (el) {
el[0].spinBeside(‘Validating’, this.form.id);
el[0].disabled = true; // Simple stopping of double submitting
}
this.doRequest(this.validateUrl + ‘form/form/’ + this.type, true);
},
** And this part is in the onComplete function after failure is set to halt after token error – see below **
if (!data.messages && data.token_error)
{
var html = ‘Critical error: ‘ + data.token_error;
html += ”;
el[0].value = “X”;
el[0].disabled = true;
el[0].addClassName(‘error’);
new Insertion.After(el[0], html);
}
* Server part (see http://phpsecurity.org/ch02.pdf page 27) *
*- index.php -*
if (isset($_POST) && $_POST != array())
{
if (!isset($_SESSION['token'])
|| $_POST['token'] != $_SESSION['token'])
{
die(“{’sucess’: false, ‘token_error’: ‘Invalid token – either you have submitted the form twice or you are an unauthorised user. Please contact admin if this is not correct.’}”);
}
$token_age = time() – $_SESSION['token_time'];
$max_token_age = 60*60*3;
if ($token_age > $max_token_age)
{
die(“{sucess: false; ‘token_error’: ‘Token timeout – you have been away for to long, form invalid.’}”);
}
}
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();
*- FormValidateController.php -*
protected function sendJsonResponse($return){
// Reset token since AJAX causes multiple calls with the same SESSION token
$_SESSION["token"] = $_POST["token"];
echo (Zend_Json::encode($return));
die();
}