Jump to content

PHP Programming/OOP5/Advanced Input validation

From Wikibooks, open books for an open world
<?php
/* created by nemesiskoen */

$pv = new InputValidator($_POST);

if($pv->exists('submit')) { // is the value 'submit' set in the $_POST array?

    $pv->hasValue('age', 'You must enter an age');
    $pv->hasValue('email', 'You must enter an email');
    // ...

    $pv->isInt('age', 'You must enter a valid age.');
    $pv->isEmail('email', 'You must enter a valid email.');
    $pv->hasMinLength('username', 2, "You're username needs to be at least 2 characters long.");
    $pv->matchbool(someFunction(), 'The function returned false!');
    $pv->matchRegex('username', REGEX_HERE, 'error message here');
    $pv->equals('password1', $pv->get('password2'), "Passwords don't match.");
    $pv->equals('password1', $pv->get('username'), "Password can't be the same as your username.");
    $pv->isUrl('website', 'You must enter a valid website.');

    if($pv->render()) {
        // form successfully submitted!
    }

}

$pv->assignToTemplate($tpl); // if you work with a template parser this will assign ALL values again, with the same name, but prefixed by 'p'.

// otherwise you can query the error array as followed:
$errors = $pv->getErrors();
// loop through the errors
echo 'The form couldn\'t submt because: <br />';
foreach($errors as $v) {
    echo $v . '<br />';
}

?>

<?php

/**
 * @package DP_InputValidator
 *
 */

class DP_InputValidator_Abstract {
	
	/**
	 * Mainarray
	 *
	 * @access protected
	 * @var array $_array
	 */
	
	protected $_array = array();
	
	/**
	 * All errorstring
	 *
	 * @access protected
	 * @var array $_errorStrings
	 */
	
	protected $_errorStrings = array();
	
	/**
	 * Errorstrings
	 *
	 * @access public
	 * @var string $noValueError
	 * @var string $noIntError
	 * @var string $noEmailError
	 * @var string $noRegexError
	 * @var string $equalError
	 * @var string $noEqualError
	 * @var string $noMinLengthError
	 */
	
	public $noValueError = "%s has no value";
	public $noIntError = "%s is no int";
	public $noEmailError = "%s is not a valid email";
	public $noRegexError = "%s doesn't match a certain regex";
	public $equalError = "%s equals something that isn't allowed!";
	public $noEqualError = "%s doesn't equal something, which is required!";
	public $noMinLengthError = "%s must be a certain length!";
	public $noIssetError = "%s is not set!";
	
	/**
	 * Set the array to validate
	 *
	 * @access protected
	 * @param array &$array
	 * @param bool $safe
	 */	
	protected function __construct(&$array, $safe = true) {
		$this->_array = $array;
		if($safe) {
			$array = null;
		}
	}
	
	/**
	 * Get a secured item of the array
	 *
	 * @access public
	 * @param string $key
	 * @return mixed
	 */
	public function get($key) {
		return $this->_secureArray($this->_array[$key]);
	}

	/**
	 * Get a raw item of the array
	 *
	 * @access public
	 * @param string $key
	 * @return string
	 */   
	public function value($key) {
		return $this->_array[$key];
	}
 
	/**
	 * Get the whole array, if secured is set to true then the array will be secured
	 * 
	 * @access public
	 * @param bool $secure = true
	 * @param mixed $noSubmit = 'submit'
	 * @return array
	 */
	public function getArray($secure = true, $noSubmit = 'submit') {
		$array = $this->_array;
		if($secure) {
			foreach($array as $k => $v) {
				if(is_array($v)) {
					$array[$k] = $this->_secureArray($v);
				} else {
					$array[$k] = htmlsecure($v);
				}
			}
		}
		
		if($noSubmit) {
			unset($array[$noSubmit]);
		}
		
		return (array) $array;		
	}
	
	/**
	 * Render method 
	 * if a fault has a occurred, an no exception is thrown: 
	 * this function will throw an exception (depending of the $throw parameter)
	 * If the $reset parameter is set to true, all the errorArrays will be reset
	 * throws Exception
	 *
	 * @access public
	 * @param bool $reset
	 * @return bool
	 */
	public function render($reset = true) {
		$checks = $this->_getAllErrorArrays();
		$aantal = 0;
		foreach($checks as $v) {
			$var = '_' . $v;
			$count += count($this->$var);
			if($reset) $this->$var = array();
		}
		$return = ($count == 0 && count($this->_errorStrings) == 0);
		if($reset && $return) $this->_errorStrings = array();
		return $return;
	}
	
	/**
	 * Set a var, override if $override is set on true
	 *
	 * @param string $var
	 * @param string $value
	 * @param bool $overwrite
	 */
	public function setVar($var, $value = "", $overwrite = true) {		
		if(is_array($var)) {
			foreach($var as $k => $v) {
				$this->setVar($k, $v, $value);
			}
		} elseif(!isset($this->_array[$var]) || $overwrite) {
			$this->_array[$var] = $value;
		}
	}
	
	/**
	 * Unset a var
	 *
	 * @param string $varName
	 */
	public function unsetVar($varName) {
		if($this->exists($varName)) {
			unset($this->_array[$varName]);
		}
	}
	
	/**
	 * Get all the occurred errorStrings, if the $multiD parameter is set to true
	 * the return array will be multiDimensional
	 * otherwise it will be a 1D array
	 *
	 * @access public
	 * @param bool $multiD = false
	 * @return array
	 */ 
	public function getErrorStrings($multiD = false) {
		if($multiD) {
			return $this->_errorStrings;
		}
		
		$return = array();
		foreach($this->_errorStrings as $array) {
			foreach($array as $value) {
				$return[] = $value;
			}
		}
		
		return (array) $return;
	}
	
	
	/**
	 * Assign the errors to a templatePower template.
	 *
	 * @access public
	 * @param string $tpl
	 * @param string $block
	 * @param string $var
	 * @return InputValidator_Abstract
	 */
	public function assignToTemplate($tpl) {
		$tpl->assign($this->getArray(), true, false, 'p_');
		$errorstrings = $this->getErrorStrings();
		
		$tpl->assign('error', count($errorstrings) > 0);
		$tpl->assign('errors', $errorstrings);
		
		return $this;
	}
	
	/**
	 * If one of the 'check'-methods is given an array as argument, this method will handle this
	 *
	 * @access protected
	 * @param bool $multiD
	 * @return array
	 */ 
	protected function _handleArray($keys, $function, $errorString) {
		$ok = true;
		foreach($keys as $v) {
			if(!$this->$function($v, $errorString)) $ok = false;
		}
		return $ok;
	}
	
	/**
	 * Secure a multi-dimensional array or a string
	 *
	 * @access protected
	 * @param mixed $array
	 * @return mixed
	 */ 
	protected function _secureArray($input) {
		$return = '';
		if(is_array($input)) {
			$return = array();
			foreach($input as $k => $v) {
				if(is_array($v)) {
					$return[$k] = $this->_secureArray($v);
				} else {
					$return[$k] = htmlentities($v);
				}
			}
		} else {
			$return = htmlentities($input);
		}
		return $return;
	}
	
	/**
	 * Add an error string
	 * if alternative is given, it will be used in the 'sprintf' statement
	 *
	 * @access protected
	 * @param string $key
	 * @param string $string
	 * @param string $alternative = ""
	 * @return bool (false)
	 */
	  protected function _addErrorString($key, $string) {
		if(!isset($this->_errorStrings[$key])) {
			$this->_errorStrings[$key] = array();
		}
		$this->_errorStrings[$key][] = $string;
		return false;
	}
	
	/**
	 * This function will return all the error Arrays that are used by the 'check'-methods
	 *
	 * @access protected
	 * @param void
	 * @return array
	 */
	protected function _getAllErrorArrays() {
		return array('noValue', 'noInt', 'noEmail', 'noRegex', 'equals', 'noEquals', 'noMinLength');
	}
	
	
	/**
	 * Process an error
	 *
	 * @access protected
	 * @param string $type
	 * @param string $key
	 * @param string $errorString
	 * @return InputValidator_Abstract
	 */
	protected function _processError($type, $key, $errorString) {
		if(isset($this->$type) && is_array($this->$type)) {
			array_push($this->$type, $key);
			$this->_addErrorString($key, $errorString);
		}
		return $this;
	}
	
	/**
	 * Is called by __call when the user wants to withdraw an error
	 *
	 * @access protected
	 * @param string $method
	 * @param array $arg
	 * @return string
	 */
	protected function _handleFetchError($method, $arg) {
		list($key, $name, $string) = $arg;
		$errorstring = $method . "Error";
		$n = $name != null ? $name : $key;
		$errstr = $string != null ? $string : $this->$errorstring;
		return in_array($key, $this->$var) ? sprintf($errstr, $n) : "";
	}
	
	/**
	 * Is called by __call to validate a field.
	 *
	 * @access protected
	 * @param string $method
	 * @param array $arg
	 * @return mixed
	 */
	protected function _handleValidate($method, $arg) {
		$key = $arg[0];
		if(is_array($key) && (count($key) == 1 || count($key) == 2)) {
			return $this->_handleArray($key, $method, $arg[1]);
		} else {
			if(is_bool($key)) {
				if(!in_array($method, $this->_boolFunctions)) 
					return false;
			} elseif(!$this->exists($key) && $method != "_Validate_isset") {
				return false;
			}
			if(!call_user_func_array(array($this, $method), $arg)) {
				return $this->_addErrorString($key, end($arg));
			}
		}
		return true;
	}
	
	/**
	 * The __call method is used to process to situations:
	 * - if you want to check for errors
	 * - if you want to withdraw the errors
	 * 
	 * @access protected
	 * @param string $method
	 * @param array $arg
	 * @return mixed
	 */
	protected function __call($method, $arg) {
		
		$validateMethod = '_Validate_' . $method;
		/*
		 if the method exists "_Validate_' . $method" try to call it with 2 or 3 arguments
		 the rest will be handled as a public method itself, and not via __call
		*/
		if(method_exists($this, $validateMethod)) {
			return $this->_handleValidate($validateMethod, $arg);
		}
		/*
		 Otherwise lets see if the requested array is set, if the call is for example:
		 _noInt('age', 'leeftijd', '%s moet een getal zijn')
		*/
		$var = '_' . $method;
		if(isset($this->$var) && is_array($this->$var) && $this->$var != $this->_array) {
			return $this->_handleFetchError($method, $arg);
		}
		return null;
	}

}

?>

Add new methods to this class to validate.

<?php

/**
 * @package DP_InputValidator
 *
 */

class DP_InputValidator extends DP_InputValidator_Abstract {
	
	/**
	 * All error arrays
	 *
	 * @access protected
	 * @var array $_noValue
	 * @var array $_noInt
	 * @var array $_noEmail
	 * @var array $_noRegex
	 * @var array $_equals
	 * @var array $_noEquals
	 * @var array $_noMinLength
	 */
	
	protected $_noValue = array();
	protected $_noInt = array();
	protected $_noEmail = array();
	protected $_noRegex = array();
	protected $_equals = array();
	protected $_noEquals = array();
	protected $_noMinLength = array();
	protected $_noIsset = array();
	
	/**
	 * An array of all the functions that accept a boolean instead of a key.
	 * 
	 * @access protected
	 * @var array $_boolFunctions
	 */
	
	protected $_boolFunctions = array('_Validate_matchBool');
	
	/**
	 * Pass through to the Parent Constructor
	 *
	 * @param array &$array
	 * @param bool $safe
	 */	
	
	public function __construct(&$array, $safe = true) {
		parent::__construct($array, $safe);
	}
 
	/**
	 * Does an array key exist
	 *
	 * @param string $key
	 * @return bool
	 */ 
	public function exists($key) {
		if(is_string($key) || is_int($key)) {
			return array_key_exists($key, $this->_array);
		}
		return true;
	}
	
	/**
	 * Does it have a value?
	 *
	 * @access protected
	 * @param string $key
	 * @return bool
	 */ 
	public function _Validate_hasValue($key) {
		return ($this->_array[$key] != "");
	}
	
	/**
	 * Is it an integer?
	 *
	 * @access protected
	 * @param string $key
	 * @return bool
	 */ 
	public function _Validate_isInt($key) {
		return (strval(intval($this->_array[$key])) == $this->_array[$key]);
	}
	
	/**
	 * Is it a valid email?
	 *
	 * @access protected
	 * @param string $key
	 * @return bool
	 */ 
	public function _Validate_isEmail($key) {	  
		if(!validateEmailFormat($this->_array[$key])) { // ADD YOUR OWN EMAIL VALIDATION FUNCTION HERE
			return ($this->_array[$key] == '');
		}
		
		return true;
	}
	
	/**
	 * Is it a valid url?
	 *
	 * @access protected
	 * @param string $key
	 * @return bool
	 */
	public function _Validate_isUrl($key) {
		if(!validateUrlFormat($this->_array[$key])) {
			return ($this->_array[$key] == '');
		}
		
		return true;
	}
	
	/**
	 * Is it set?
	 * 
	 * @access protected
	 * @param string $key
	 * @return  bool
	 */
	public function _Validate_isset($key) {
		return isset($this->_array[$key]); 
	}
	
	/**
	 * Does it match a given regex?
	 * 
	 * @access protected
	 * @param mixed $key
	 * @param string $match
	 * @return bool
	 */ 
	public function _Validate_matchRegex($key, $match) {
		
		return (preg_match($match, $this->_array[$key]));
		
	}
	
	/**
	 * Does it equals '$value'?
	 * 
	 * @access public
	 * @param mixed $key
	 * @param string $value
	 * @param bool $case
	 * @return bool
	 */	 
	public function _Validate_equals($key, $value, $case) {		   
		if($case == false) {
			return (strtolower($this->_array[$key]) === strtolower($value));
		}
		return ($this->_array[$key] === $value);
			
	}
	
	/**
	 * Does the length differs from '$value'?
	 * 
	 * @access public
	 * @param mixed $key
	 * @param string $value
	 * @return bool
	 */	 
	public function _Validate_noEquals($key, $value) {
		
		return ($this->_array[$key] === $value);
	}
	
	/**
	 * Is the length longer than $length?
	 * 
	 * @access public
	 * @param mixed $key
	 * @param string $value
	 * @return bool
	 */
	public function _Validate_hasMinLength($key, $length) {
		
		return (strlen(trim($this->_array[$key])) >= $length);
		
	}	
	
	/**
	 * Is a bool true or false
	 * USE:
	 * $pv->matchBool(memberCheck($pv->get('member')), 'The member is already registered')
	 * 
	 * @access public
	 * @param mixed $key
	 * @return bool
	 */ 
	public function _Validate_matchBool($bool) {
		return $bool ? true : false;
	}

}

?>