Form Validation

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Form Validation

Post by Luke »

There has to be a better way to validate forms than this:

Code: Select all

// Title Valitation
		$title = $Filter->stripTags($request->get('title'));
		$errors['title'] = $Filter->checkLength($title, $title_max_length, 1) ? false : "Title Must be between 1 and " . $title_max_length . " characters in length.";
		$Template->assign('title_error', $errors['title']);
		$Template->assign('title', $title);

		// Body Valitation
		$body = $request->get('body');
		$errors['body'] = $Filter->checkLength($body, $body_max_length, 1) ? false : "Body Must be between 1 and " . $body_max_length . " characters in length.";
		$Template->assign('body_error', $errors['body']);
		$Template->assign('body', $body);
Any advice on validating forms and reporting errors back to the user would be GREATLY appreciated, as this is what I always do (I haven't used objects in the past, but this method really is just procedural code with objects)

Thanks! :)
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Encapsulate the actual form post in an object.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Elaborate... a specific class for every form? A form class with children classes per form?
Last edited by Luke on Thu Aug 03, 2006 5:39 pm, edited 1 time in total.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

It will look like this when it's done:

Code: Select all

$form = new Form($_POST);
$status = $form->validate();
if ($status) {
  // display success
} else {
  // redisplay the form
  $form_printer->paint($form);
}
Internally speaking, validate() might look like this:

Code: Select all

// retrieve the values
foreach ($this->fields as $name) {
  $this->values[$name] = isset($post[$name]) ? $post[$name] : $this->defaults[$name];
}
// validate the values
$status = true;
foreach ($this->values as $name => $value) {
  // only validate if there's a validator present
  if (isset($this->validators[$name])) {
    $validity = $this->validators[$name]->validate($value);
    if (!$validity->isValid()) $status = false;
    $this->errors = array_merge($this->errors, $validity->getErrors());
  }
}
return $status;
I'll leave the rest of the details to your imagination.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

thanks, that makes a lot of sense!
User avatar
volka
DevNet Evangelist
Posts: 8391
Joined: Tue May 07, 2002 9:48 am
Location: Berlin, ger

Post by volka »

And you might want to take a look at http://pear.php.net/package/HTML_QuickForm/
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Anybody have any other methods?
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

OsisForms!
xD

With that you would be able to do something like:

Code: Select all

$f = new OF_Form('f'); 
{
    $title = new OF_SmallText('title', 'Title');
    $title->addFilter('noTags');
    
    $body = new OF_LargeText('body', 'Body');
    $body->width = 40; $body->height = 10;
    
    $f->addEntities($title, $body);
}
if ($f->submitted()) {
    foreach (array('title', 'body') as $fieldName) {
        $length = $fieldName . '_max_length';
        if (!$$fieldName->hasLengthBetween(1, $$length)) {
            $$fieldName->addError(ucfirst($fieldName) . ' Must be between 1 and ' . $$length . ' characters in length.');
        }
    }
    if ($f->getNumErrors()) {
        // errors
    } else {
        // no errors
    }
}
$Template->assign('form', $f);
But I need help finishing writing it first :(
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Here is what I did... and once again (I find myself doing this often) I am wondering if I missed the point:

Form.inc.php

Code: Select all

<?php
require('Validator.inc.php');
class Form{
	
	private
	$data = array(),
	$validators = array(),
	$errors = array();
	
	public function __construct($data){
		$this->data = $data;
	}
	
	public function addValidator($name, $rule, $error){
		if(array_key_exists($name, $this->data)){
			$validator = new Validator($name, $rule, $error);
			$this->validators[$name] = $validator;
		}
	}
	
	public function validate(){
		foreach($this->validators as $name => $validator){
			if(!$validator->isValid()) $this->errors[$name] = $validator->getError();
		}
		return empty($this->errors);
	}
	
	public function getErrors(){
		return $this->errors;
	}
	
}
?>
Validator.inc.php

Code: Select all

<?php
class Validator{
	
	private
	$error,
	$name,
	$rule;
	
	public function __construct($name, $rule, $error){
		$this->name = $name;
		$this->rule = $rule;
		$this->error = $error;
	}
	
	public function isValid(){
		return $this->rule;
	}
	
	public function getError(){
		return $this->error;
	}
}
?>

Code: Select all

$Form = $Registry->get('addform');
		
		$Form->addValidator('body',
							$Filter->checkLength($request->get('body'), $body_max_length, 1),
							"Must be between 1 and " . $body_max_length . " characters in length."
							);
		
		if($Form->validate()) echo "Form is valid";
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Code: Select all

public function addValidator($name, $rule, $error){
                if(array_key_exists($name, $this->data)){
                        $validator = new Validator($name, $rule, $error);
                        $this->validators[$name] = $validator;
                }
        }
Personally wouldn't do that. What if you have validations dependent on other validations, or the database, or the user permissions? You can't hope to successfully abstract validation logic without making things really complicated.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

ole wrote:

Code: Select all

public function addValidator($name, $rule, $error){
                if(array_key_exists($name, $this->data)){
                        $validator = new Validator($name, $rule, $error);
                        $this->validators[$name] = $validator;
                }
        }
Personally wouldn't do that. What if you have validations dependent on other validations, or the database, or the user permissions? You can't hope to successfully abstract validation logic without making things really complicated.
don't follow you

But... I would like to know how I could send the validator a rule to follow... how could I do that?
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Well say you have a form for booking what you are going to eat at a resturant.

You can choose a starter, main and dessert from 3 <select> boxes. Above these selects are a tick boxes which you can tick or untick to specify whether you are going to have that particular the course. Because some people may not want a starter or dessert. Obviously if a user ticks that they want a dessert you have to validate that they have choosen from one of the available dessert options in the select and not just given you any old value so that is a 'Validation Rule' but the existance of that rule is conditional. You only need to do that check if they have choosen they want a dessert in the first place with the tick box.

That was a really simple example, these things can actually get quite a lot more complex if you are building web applications.

In OsisForms I solve this by providing a simple set of methods for validation but not by abstracting the validation itself. This way you still use logic to make the validations, which means you can achieve conditional validations and in fact pretty much anything but much the validation easier and more readible.

Code: Select all

if ($f->submitted()) {
    if ($chkDessert->getInput()) {
        if (!$selectDessert->isOneOf()) {
            $selectDessert->addError('You must choose from one of the available options');
        }
    } else {
        // don't bother =xD 
    }
}
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

:? my brain hurts... I'm gonna go play my guitar for a while.


I'll be back.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I am not sure if you have looked at the Skeleton code, but there are examples of Validator/Rule and FilterChain/Filter classes for this. There are also examples of a generic Input Controller class and state/transition based Application Controller -- and an example Form Controller built on top of it. There are code examples using these as well showing how to do form validation and display error messages for field errors.
(#10850)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

oh yea... I forgot about those. I'll take a look in a minute. :D

Arborint... if your code was commented and PHP5, I would love you. hehe... it's great and I borrowed about 100 things from it, but it takes me a long time to figure stuff out because it isn't commented. :(
Post Reply