Help with DRY and Form Processing

I am trying to rewrite old procedural forms with form processing that repeats alot of code. I thought a class/OOP would work best and being influenced by some of the form classes, I just wrote a simple class (still a beginner, please be kind).


abstract class FormProcess {
	var $_valArray = array();
	var $_valError = array();

	function addValidation( $field, $callback ) {
		$this->_valArray[] = array(
			'field' => $field,
			'callback' => $callback
		);		
	}
	
	function validate() {
		$array = array();
		
		foreach( $this->_valArray as $v ) {
			$validated = $v['callback'](
				$_POST[$v['field']]
			); 
			// Expecting an array back from the callback
			if ( isset($validated['error']) ) {
				$this->valError[$v['field']] = $validated['error'];
			} else {
				$array[$v['field']] = $validated['value'];
			}
		}
		return $array;
	}
	
	function hasError() {
		if (!empty($this->valError)) {
			return TRUE;
		}
	}	
	
	abstract function send();
}

The problem is, when I start defining the forms, the callback functions and the send() method seem to repeat themselves and I would to keep the DRY element.


<!DOCTYPE html>
<html>
	<head>
		<title>Test Form</title>
	</head>
	<body>
<?php

class ExampleForm extends FormProcess {
	private $formName;

	public function __construct( $form_name ) {
		$this->formName = $form_name;
		
		// For the required inputs specific to this form
		$this->addValidation('ip', 'checkIP');
		$this->addValidation('time', 'checkTime');
	}

	public function formReqInput() {
		return '	<input type="hidden" name="ip" value="'. $_SERVER['REMOTE_ADDR'] .'" />
			<input type="hidden" name="time" value="'. time() .'" />';	
	}
	
	// Example of a send method
	public function send() {
		if ( isset($_POST['submit-'.$this->formName]) ) {
			$validated = $this->validate();
			
			if ($this->hasError()) {
				echo 'Error Found!';
				print_r($this->valError);
			} else {
				echo 'Sending Message';
				// go though the validated['values']
			}
		}
	}
}

function checkName($name) {
	if ($name == '') {
		$var['error'] = 'The name is empty.';
	} else {
		$var['value'] = trim($name);
	}
	return $var;
}

function checkSubject($subject) {
	if ($subject == '') {
		$var['error'] = 'The subject is empty';
	} else {
		$var['value'] = trim($subject);
	}
	return $var;	
}
// The rest would follow the same pattern...
function checkMessage($message) {}
function checkIP($ip) {}
function checkTime($time) {}

$c = new ExampleForm('testForm');
// For the example I will note only 2 validators
$c->addValidation('name', 'checkName');
$c->addValidation('subject', 'checkSubject');
$c->send();

?>
	
		<form action="form.php" method="post" name="testForm">
			<p>
				<label>Name:</label>
				<input type="text" name="name" value="" />
			</p>
			<p>
				<label>Subject:</label>
				<input type="text" name="subject" value="" />
			</p>	
			<p>
				<label>Message:</label>
				<textarea name="message"></textarea>
			</p>
			<p>
				<?php echo $c->formReqInput(); ?>
				<input type="submit" name="submit-testForm" value="Submit Form" />
			</p>
		</form>	
<?php

// class SearchForm, pretty much is a clone to ExampleForm without the FormReqInputs and thier respectable validation...
class SearchForm extends FormProcess {
	private $formName;

	public function __construct( $form_name ) {
		$this->formName = $form_name;
	}	
	public function send() {
		if ( isset($_POST['submit-'.$this->formName]) ) {
			$validated = $this->validate();
			
			if ($this->hasError()) {
				echo 'Error Found!';
				print_r($this->valError);
			} else {
				echo 'Sending Query!';
			}
		}
	}
}

function checkSearchQuery( $query ) {
	if (strlen($query) < 4 || $query == 'Search Keywords') {
		$var['error'] = 'There was an error in the input provided, please try again.';
	} else {
		$var['value'] = trim($query);
	}
	return $var;
}

$s = new SearchForm('searchForm');
$s->addValidation('search_query', 'checkSearchQuery');
$s->send();

?>		
		<form action="form.php" method="post" name="searchForm">
			<p>
				<input type="text" name="search_query" id="keywords" value="Search Keywords" />
				<input type="submit" name="submit-searchForm"  value="Submit Search" />
			</p>
		</form>		
	</body>
	<script type="text/javascript">
		window.onload = searchField();

		function searchField() {
			var kw = document.getElementById("keywords");
			kw.onfocus = function() {
				kw.value = '';
			}
			kw.onblur = function() {
				if (kw.value == '') {
					kw.value = "Search Keywords";
				}
			}
		}
	</script>	
</html>

Also any critique of the class is helpful as well, am I missing anything important? I don’t need the form to be rendered, hence why I am not using a framework or the Zend Form class.

The HTML was provided in the first post in the second code block. Here it is as ran in the browser.

<!DOCTYPE html>
<html>
	<head>
		<title>Test Form</title>
	</head>
	<body>
		<form action="form.php" method="post" name="testForm">
			<p>
				<label>Name:</label>
				<input type="text" name="name" value="" />
			</p>
			<p>
				<label>Subject:</label>
				<input type="text" name="subject" value="" />
			</p>	
			<p>
				<label>Message:</label>
				<textarea name="message"></textarea>
			</p>
			<p>
				<input type="hidden" name="ip" value="::1" />
				<input type="hidden" name="time" value="1278932282" />				
				<input type="submit" name="submit-testForm" value="Submit Form" />
			</p>
		</form>	
		
		<form action="form.php" method="post" name="searchForm">
			<p>
				<input type="text" name="search_query" id="keywords" value="Search Keywords" />
				<input type="submit" name="submit-searchForm"  value="Submit Search" />
			</p>
		</form>		
	</body>
	<script type="text/javascript">
		window.onload = searchField();

		function searchField() {
			var kw = document.getElementById("keywords");
			kw.onfocus = function() {
				kw.value = '';
			}
			kw.onblur = function() {
				if (kw.value == '') {
					kw.value = "Search Keywords";
				}
			}
		}
	</script>	
</html>

If i submit the top form with nothing I get as expected.

Error Found!Array
(
    [name] => The name is empty.
    [subject] => The subject is empty
)
	

With the second form:

Error Found!Array
(
    [search_query] => There was an error in the input provided, please try again.
)

It is the field name and the (possible) error message.

That is one of the things I was looking for. Can you give me an example of this and the return information?

Maybe you should not use the $_POST array directly in the class,in case you want to use the form class with the GET method

Good idea.

Can you provide the html source from the browser when this runs? It all looks pretty good to me. The only thing you might like to do is make the validation functions into classes so you can have a normalised way of getting errors etc.

So make a base validation class with common methods and if I need specific situations, I can extend the class… like the below?


class FormValidator {
	var $_input;
	var $_error = FALSE;
	var $_failed = array();
	
	function __construct($input) {
		$this->_input = $input;
	}
	
	function minLength( $num ) {
		if ( !strlen($this->_input) > $num ) {
			$this->_error = TRUE;
			$this->_failed[] = 'minLength';
		}
	}
	
	function maxLength( $num ) {
		if ( !strlen($this->_input) < $num ) {
			$this->_error = TRUE;
			$this->_failed[] = 'maxLength';
		}
	}
	
	function hasError() {
		if ($this->_error === TRUE) {
			return TRUE;
		}
	}
	
	function getFailedVal() {
		return implode(', ', $this->_failed);
	}

} 

$f = new FormValidator('blah');
$f->minLength(3);
$f->maxlength(5);
if ($f->hasError()) {
	echo 'Error in validator!<br>';
	echo 'Failed on '.$f->getFailedVal();
}

Maybe you should not use the $_POST array directly in the class,in case you want to use the form class with the GET method

Since the send method is almost identical, I’d probably add that into the base class and maybe pass the two messages (error and success) as parameters. If the send method differs, then you can just overload it.

To make validation easier, you might want to go for something along the lines of:

$val = new formValidation('search_query');

// Method chaining.
// Of course you could do: $val->minLength(4); $val->canBeEmpty(false); $val->notEquals('Search Keywords');
$val->minLength(4)->canBeEmpty(false)->notEquals('Search Keywords');

$s->addValidation('search_query', $val);