Advanced 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
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Which part is reduntant? Do you mean a "class" for every field or an "object" for every field? Are you talking about the PHP side or the Javascipt side?
Now that I think about it, a class for every field type is not what I meant to say. As you stated an object for every field.
The thing about "fields" for me is that they potenially need a bunch of stuff for them.
Agreed.
For a form each data field needs a one or more filters, one or more validators, plus error messages, and potentially one or more callbacks that run on some state change (load/submit/accepted). That's just the data fields.
IMHO I don't think this is a very effective way of handling your field values. How about

Code: Select all

$fields = array(
   'username' => array('isAllLetters', 'isNotNull'),
   'email' => array('isEmailFormat')
);

$validated = new fields($fields, $_POST);

class fields extends fieldValidation
{
   function __construct($fieldsArray, $source) {
      $this->fields = $fieldArray;
      $this->validateRules($fieldsArray);
      $this->method = $source;
   }

   function getField($field) {
      if (!array_key_exists($field, $this->errors) && isset($this->fields[$field])) {
         return $this->fields[$field];
      }
      return false;
   }

   function assignRules() {
      foreach ($fieldsArray as $field => $rules) {
         if (is_array($rule)) {
            foreach ($rules as $rule) {
               if (!$this->validate($rule)) {
                  $this->errors[$field][] = $this->getError($rule);
               }
            }
         }
         else {
            if (!$this->validate($rule)) {
               $this->errors[$field][] = $this->getError($rule);
            }
         }            
      }
   }
}

class fieldValidation 
{
      function _construct() { }
 
      function validate($field, $rule) {
         if (method_exists($this, $rule) && (!empty($this->method[$field])) {
            if ($this->$rule($field)) {
               return true;
            }
            $this->addError($field);
         }
      }
}
I just wrote that quick to get my point across, no idea if it actually works or not.
If you think about a general architecture -- a parameter could be an Event that triggers an action.
Yea..

Code: Select all

if ($validated->getField('action') !== false) {
   //trigger event
}
The point of a controller is to create a reusable system rather than custom coding every form.
Your way has no more reusability than my idea.. IMO. Can you show otherwise please?
And for me encapsulating all the stuff for each potential parameter makes sense.
I prefer to encapsulate all closely related stuff.
The question is how to do it.
My way or the highway. :lol: Joking, of course.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Jcart wrote:IMHO I don't think this is a very effective way of handling your field values. How about
OK ... get your code. But according to your code you would need to have all the rules available as methods of the class fieldValidation. That does not seem very modular. If you want to add more rules do you need to extend the class to add methods? Maybe this is a question of inheritance vs composition?

Code: Select all

if (!$this->validate($rule)) {
// which would become
               if (!$this->validate('isAllLetters')) {
// which would call
$this->isAllLetters('username');
How does your "trigger event" code relate to the rest of the code?
Jcart wrote:I prefer to encapsulate all closely related stuff.
So we would need to agree what does "closely" mean as it relates to forms parameters.
Jcart wrote:My way or the highway. :lol: Joking, of course.
I am interested in hearing about everyone's highway because I always seem to learn something new when I travel down one. :)
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

Personally, I prefer a class for each rule. With a unified interface, rules are entirely interchangable. Which has amazing power potential and VERY easy to extend/customize.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I have found that one of the intereting byproducts of using a Rule and Validator based system come when you create larger structures that also have an isValid() method as well. This type of polymorphicism can allow for things like multiple form controllers on a page each running a Validator chain with the controllers within an outer Validator. It may sound a little wacky a first, but if you want to build a wizard type thingy (e.g. checkout) using an Application Controller then the code reuse starts to make a lot of sense.

If I get some time I will try to code up an example show a fuller form controller example. Maybe Jcart could expand his and we could all learn in the process. I have been thinking about how I could combine the two.
(#10850)
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Interesting.. I have 10 days off so I can actually contribute this time :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I'll throw some Filter/FilterChain code into the mix.

Code: Select all

<?php

class FilterChain {
var $chain = array();

function addFilter (&$filter) {
	if (is_array($filter)) {
		$this->chain = array_merge($this->chain, $filter);
	} else {
		$this->chain[] = $filter;
	}
}
	
function doFilter ($value, $filter=null) {
	if ($filter) {
		$this->chain = $filter;
	}
	foreach ($this->chain as $filter) {
		$value = $filter->doFilter($value);
	}
	return ($value);
}
	
} // end class Filter


/*
 * Abstract class
 */
class Filter {
	function doFilter ($value) {
		trigger_error("Filter::doFilter is abstract!", E_USER_ERROR);
	}
}


class FilterRegexp extends Filter {
var $from = '';
var $to = '';

function FilterRegexp ($from, $to='') {
	$this->from = $from;
	$this->to = $to;
}
	
function _filter_fix_from ($from) {
	if($from) {
		if(substr($from, 0, 1) != '/') {
			$from = '/' . $from;
		}
		if(substr($from, -1) != '/') {
			$from .= '/';
		}
	}
	return $from;
}

function doFilter ($value) {
	if (is_array($this->from)) {
		foreach ($this->from as $key => $val) {
			$this->from[$key] = $this->_filter_fix_from($val);
		}
	} else {
		$this->from = $this->_filter_fix_from($this->from);
	}
	return preg_replace($this->from, $this->to, $value);
}

} // end class FilterRegexp


class FilterLength extends Filter {
var $length = 0;

function FilterLength ($length) {
	$this->length = $length;
}
	
function doFilter ($value) {
	if ($this->length < strlen($value)) {
		$value = substr($value, 0, $this->length);
	}
	return $value;
}

} // end class FilterLength


class FilterToUpper extends Filter {

function doFilter ($value) {
	return strtoupper($value);
}

} // end class FilterToUpper


class FilterToLower extends Filter {

function doFilter ($value) {
	return strtolower($value);
}

} // end class FilterToLower

?>
(#10850)
User avatar
quocbao
Forum Commoner
Posts: 59
Joined: Sat Feb 04, 2006 2:03 am
Location: HCM,Vietnam
Contact:

Post by quocbao »

Adding client side validation is not a bad idea. There are many examples and scripts out there. However, remember to always start with a solid serverside validation. Javascript can always be disabled. Sometimes on purpose, other times someone accessing your site has no other choice. This is also important for the accessibility of your forms/applications.
Yes , ClientSide is not trustable :)
An approach I really would like to take myself is combine both sides in some way.
That's what i want to do .
first is the idea to let the PHP output the js
One idea , and i think this is the most suitable way . And i think we should also have a js library , and PHP just output the validate code .
using hidden form fields which tell the server what type of validation to perform
Absolutely NO , a hacker can easily remove these field to pass your validation . This is not safe for your input.

Thanks all for your code .

But let me tell what i think :

Actually , i don't really care about Validation Class Object ("How it works") .

Here're my problems when using the old ways

This is the old way

Code: Select all

$validator = new Validator();
$validator->addRule(new RuleNotNull('username', 'Username required'));
$validator->addRule(new RuleLength('username', 3, 30, 'Username must be 3 to 20 characters long'));
$validator->validate($_POST);
First: for complicated form like Credit card input form , this is really complicated , and of course , your "Validation code" is complicated too . And what happen when you have to add a new field or update rule or something , you will have to dig into HTML and PHP code ( or also Javascript )

Second : when we have many realted form , validation rule as related too ( of course ) , so we can reuse them for many form .

Using something like RulePool may solve the problem

But i think we need a new way , an easier way :D ( Lazy coding ;) )
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Quote:
using hidden form fields which tell the server what type of validation to perform
Absolutely NO , a hacker can easily remove these field to pass your validation . This is not safe for your input.
I think I'll have to explain this a bit. First of all, it's not my idea. This method was explained in just a few words by Chris Campbell in the comments in this article.

Second, about the method: whether or not the validation takes place does NOT depend on the availability of the hidden form fields per se. Server side validation will always take place. If $_POST['email'] should contain an email address, you validate it for an email address, etc etc. Of course it would be stupid to rely on only hidden form fields.

The hidden form fields are only there as an extra layer. The hidden data is used by the js to perform the client side validation. The designer of the form (the front end guy, or in this case the "hacker") must know which hidden fields to add to which input fields, but he doesn't have to touch the server side /php.
Chris wrote:Whoever is designing the form will need to know the syntax to use for the hidden fields. It isn’t pretty but they’ll never have to touch php
But I don't think I understand or can explain the details very well. Chris promised to write an update about their ideas about this on particletree.

In the meanwhile I'm hoping to learn/develop a thing or two from the discussion(s) here.
User avatar
quocbao
Forum Commoner
Posts: 59
Joined: Sat Feb 04, 2006 2:03 am
Location: HCM,Vietnam
Contact:

Post by quocbao »

The hidden form fields are only there as an extra layer. The hidden data is used by the js to perform the client side validation. The designer of the form (the front end guy, or in this case the "hacker") must know which hidden fields to add to which input fields, but he doesn't have to touch the server side /php.
Then we also have "validation rules" on our server , and one copy from HTML . What is advantage ?
Both developer and designer will have to handle form validation :?:

But , whatever , Chris has the same idea with me :)
Whoever is designing the form will need to know the syntax to use for the hidden fields. It isn’t pretty but they’ll never have to touch php
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Then we also have "validation rules" on our server , and one copy from HTML . What is advantage ?
Both developer and designer will have to handle form validation
No, as far as I've understood that's not the case. The "designer" only has to add the correct hidden input fields to the form, like:

Code: Select all

<input type="hidden" name="required" id="required" 
value="name,emailaddress"  />
<input type="hidden" name="additional" id="additional" 
value="emailaddress/email,zipcode/zip" />
He doesn't have to touch the php or write the javascript.
But really, just check out the article I linked to. Hopefully Chris will soon write more about their system.
User avatar
quocbao
Forum Commoner
Posts: 59
Joined: Sat Feb 04, 2006 2:03 am
Location: HCM,Vietnam
Contact:

Post by quocbao »

Yes , i have read the article .
But seem like they are just clientside validation , and we need both serverside and clientside .
The "designer" only has to add the correct hidden input fields to the form, like

Code: Select all

<input type="hidden" name="required" id="required"
value="name,emailaddress"  />
<input type="hidden" name="additional" id="additional"
value="emailaddress/email,zipcode/zip" />
Then , on serverside , it will have to base on hidden input to validate data but as i mentioned before "a hacker can easily remove these field to pass your validation"
And you also said "Of course it would be stupid to rely on only hidden form fields" , does this mean on serverside we'll rely on some other things , then both developer (serverside) and designer (clientside) will have to handle form validation
Then , once again , what is the advantage ?

I hope i explain well :oops:

He ( designer ) doesn't have to touch the php or write the javascript.
Yes , this is what i ( we ) want , and i hope we can do this :)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Well, as I said before I really don't know the finer details of the implementation the guys from particletree have in mind. But I do think I understand the general direction in which they want to go. If I understood Chris in the few lines he explained it in (I'm talking about the comment he made here, not the article itself).
chris wrote:We’re actually totally rewriting the way we handle validation using JavaScript and PHP classes. I’m hoping to write a new article on it in the next month or so. Although I preferred classes at first, using hidden fields is the approach we’re going with now.
In the original example, if JavaScript was disabled, I hand coded the validation in PHP. For example, I had to know that email was validated as email and go into the PHP file and say “if $POST_[‘email’] is blank, validate it as email”. That approach is fine if you’re making a one time form for somebody and know some PHP.
What we want now is a method so nobody ever has to go into a PHP file and hidden inputs allow us to do the trick. Hopefully I get some time to write up what we’re working on now because we believe it’s a better solution.
me wrote:If I understand it a bit, you will make a kind of general class which has to receive certain kinds of input variables (from the normal and hidden fields) to be able to validate the input? So that even if javascript is (purposefully) disabled, validation takes place.
chris wrote:Exactly.
Whoever is designing the form will need to know the syntax to use for the hidden fields. It isn’t pretty but they’ll never have to touch php.
So as far as I understand, you have a general (php) class which is always used and has to receive certain input to be able to validate the data. If it does not recieve the correct combination of data from the inputfields and hidden fields it returns an error (message) and doesn't let the data go through. I think it's like sending letters and packages. Only if you use the correct packaging material will the postal service start to handle your sending.
Then, on the client side of things the same thing happens. The javascript validation class is attached to the head of the document. If js is enabled, the javascript will go through all input and hidden elements and perform the client side validation.

I hope you follow this. Remember, I'm only thinking out loud here. To answer your question
does this mean on serverside we'll rely on some other things , then both developer (serverside) and designer (clientside) will have to handle form validation
Then , once again , what is the advantage ?
The developer is responsible for a rock solid server side validation routine, as always. He does not rely on anything client side, because he knows any input can never be trusted (we assume he did his homework and he knows this ok? ;) ) What the designer only has to do is know the correct use of form fields and hidden input fields. He doesn't do anything with the js class or the server side. The designer knows that if he doesn't write the correct (combination of) input and hidden fields his form will not be validated correctly. Like if he didn't put the correct stamp on his package he is sending, he knows it will never be handled by the postal service.

Ok, this rambling has gone on too long already, time to go back to work.. :) Hope I made the idea clear and sorry if I can't help you more. If you want some more concrete help, I would suggest you post some code here and I'm sure the pro's here will be willing to help you.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

quocbao wrote: But let me tell what i think :

Actually , i don't really care about Validation Class Object ("How it works") .

Here're my problems when using the old ways

This is the old way

Code: Select all

$validator = new Validator();
$validator->addRule(new RuleNotNull('username', 'Username required'));
$validator->addRule(new RuleLength('username', 3, 30, 'Username must be 3 to 20 characters long'));
$validator->validate($_POST);
First: for complicated form like Credit card input form , this is really complicated , and of course , your "Validation code" is complicated too . And what happen when you have to add a new field or update rule or something , you will have to dig into HTML and PHP code ( or also Javascript )
Not sure what you mean by "old ways". I don't see how a credit card input form is more complicated. A RuleCreditCard class would be straightforward to write. And, if you add a new field you will have to add HTML and PHP code and Javascript to display and manage that field -- no way around that.
quocbao wrote:Using something like RulePool may solve the problem

But i think we need a new way , an easier way :D ( Lazy coding ;) )
I be interested in knowing about RulePools or your ideas on an easier way. I think there is room for improvement here.

I read the article by Chris Campbell, and though it is interesting stuff, in many ways it is a mess. Using class or other fields to communicate information they were not intended to communicate seems clunky. And I really can't imagine trying to generate that HTML with PHP -- which is what you need to do to keep the validation on the client and server sides in sync.
(#10850)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

.. and though it is interesting stuff, in many ways it is a mess
Christopher, I think I know what you mean. As Chris points out himself, all of the methods he talks about have their drawbacks.

I think in the case of form validation it really depends on the specific situation (ok, duh). But you are right to say that in some way or another the front end designers and backend people have to know from eachother what to do. Your idea of generating everything with PHP is what seems the best thing for me too. As I do both the html and php side anyways.

I've never heard of RulePools, so I would be interested in what they are as well and how they can be applied to this problem.
User avatar
quocbao
Forum Commoner
Posts: 59
Joined: Sat Feb 04, 2006 2:03 am
Location: HCM,Vietnam
Contact:

Post by quocbao »

I be interested in knowing about RulePools or your ideas on an easier way
just a little change to orginal code , to make life easier

Code: Select all

<?

class Validate
{
	function Check($pool,$input)
	{
		return $pool->isValid($input); //  , this looks funny 
	}
	
	function RenderJs($pool,$form_id='')
	{
		//create some javascript to handle clientside
		echo "some javascript ";
	}
}

class Rule
{
	var $message; //error message
	function isValid($value) {}
}

class RulePool extends Rule
{
	var $message; //error message for this pool
	var $rules = array();
	
	function RulePool($pool=null)
	{
		if ($pool && is_a($pool,'RulePool'))
		{
			$this->rules = $pool->rules; //do some inheritance
		}
	}
	
	function add($rule,$fields,$message='')
	{
		if ($message)
		{
			$rule->message = $message;
		}
		if (!is_array($fields))
		{
			//yeah , always array , so we can use ->add(rule,'field')
			//or ->add(rule,array('field','field2'))
			
			$fields = array($fields);
		}
		$this->rules[] = array(
			'rule' => $rule ,
			'fields' => $fields ,
		);
	}
	
	function isValid($values)
	{
		foreach ($this->rules as $rule_data)
		{
			$fields = $rule_data['fields'];
			$rule = $rule_data['rule'];
			
			foreach ($fields as $name)
			{
				$value = @$values[$name];
				if ($rule->isValid($value))
				{
					//overwrite error message
					$this->message = $rule->message;
					return false;
				}
			}
			
		}
		return true;
	}
	
}

class RuleNotNull extends Rule
{
	var $message = "You didn't fill one field."; //just default message 
	
	function isValid($value)
	{
		return ($value != "");
	}
}

class RuleStrLength extends Rule
{
	var $message = "You enter a field too short or too long."; //just default message 
	var $min,$max;
	
	function RuleStrLength($min=null,$max=null)
	{
		$this->min = $min;
		$this->max = $max;
	}
	
	function isValid($value)
	{
		if (is_numeric($this->min) && strlen($value) < $this->min) return false;
		if (is_numeric($this->max) && strlen($value) > $this->max) return false;
		return true;
	}
}

?>

Code: Select all

$pool = new RulePool();
//add rule
$pool->add(new RuleNotNull() , array('field1','field2','field3') , 'Please enter all requied field ');

//yeah , we can reuse this
$new_pool = new RulePool($pool);
$new_pool->add(new RuleStrLength() , array('field1','field2'));

if (Validate::Check($new_pool , $_POST))
{
       echo "Good client ";
}
else
{
      echo "Bad Client ";
      echo $new_pool->message; //show the user error message
}

//in case we need to create javascript
Validate::RenderJs($new_pool , 'myid');
I don't know it will work :D or NOT :oops: , i just write some code so you can get my idea
Last edited by quocbao on Mon Feb 20, 2006 3:47 am, edited 1 time in total.
Post Reply