Code: Select all
<?php
/**
* @package Validator
* @author Bruce Weirdan
* @version $Id: Validator.php,v 1.14 2004/06/10 01:33:13 weirdan Exp $
*/
/**
* abstract class to inherit Validators from
* @abstract
*/
class Rule {
/**
* real constructor
* @abstract Redefine it in subclasses if appropriate
* @return void
*/
function __construct() {}
/**
* destructor. Does nothing.
* @return void
*/
function __destruct() {}
/**
* fake constructor for upcoming PHP5 compatibility
*/
function Rule() {
$args = func_get_args();
register_shutdown_function( array(&$this, '__destruct') );
call_user_func_array( array(&$this, '__construct'), $args);
}
/**
* heart of validator and rules
* @return bool true if there were no errors, false otherwise
* @abstract Redefine it in subclasses
*/
function execute() {}
/**
* @return string description of error(s) (if any)
* @abstract Redefine it in subclasses
*/
function getDescription() {}
}
/**
* Main validator class. Implements slightly simplified Composite pattern
* example:
*<code>
*$val = new Validator($_REQUEST);
*$val->addRule( 'asd', new Required(new AlphaNum) );
*$val->addRule( 'sdf', new Optional(new AlphaNum) );
*var_dump($val->execute());
*var_dump($val->errors);
*</code>
*
*/
class Validator extends Rule {
/**
* @var array $rules array(string name => Rule)
* @final
*/
var $rules = array();
/**
* @var array $incoming array to validate (eg $_GET)
* @final
*/
var $incoming = array();
/**
* @var array $errors array to store errors in
* @final
*/
var $errors = array();
/**
* real constructor.
* Note: one must supress PHP notes while calling this method with no params
* by prepending it with @ sign or there might be notes issued.
* It's limitation of PHP4. Currently there is no <i>elegant</i> workaround
* for this issue.
* @param array &$operate_on array to operate on
* @return void
*/
function Validator(&$operate_on){
$this->incoming =& $operate_on;
}
/**
* add rule for name. Call this function once for each name
* @param string $for_name name to add rule for
* @param Rule $rule validator for given name
* @return bool true on successful addition, false otherwise
* @final
*/
function addRule($for_name, $rule) {
if( isset($this->rules[$for_name]) ) {
trigger_error('Rule has been already set. Use Validator instead of multirules');
return false;
}
$this->rules[$for_name] = &$rule;
return true;
}
/**
* remove rule for name. Found no usage for this as for now...
* @param string $for_name name to remove rule for
* @return bool true if rule has been removed, false on error
* @final
*/
function removeRule($for_name) {
if( !isset($this->rules[$for_name]) ) {
trigger_error('No rules has been set for ' . $for_name);
return false;
}
unset($this->rules[$for_name]);
return true;
}
/**
* heart of validator. scans $rules array and executes them on corresp. data
* Note: one must supress PHP notes while calling this method with no params
* by prepending it with @ sign or there might be notes issued.
* It's limitation of PHP4. Currently there is no <i>elegant</i> workaround
* for this issue.
* @param array &$operate_on array to operate on.
* @return bool true if there were no errors, false otherwise
*/
function execute(&$operate_on) {
if( isset($operate_on) ) {
$this->incoming =& $operate_on;
}
if( !count($this->rules) ) {
trigger_error('No rules has been set at all');
return false;
}
/*
if( !count($this->incoming) ) {
return false; //incoming is empty
}*/
foreach($this->rules as $name => $rule) {
//echo "About to execute rule for $name\n";
$was_set = isset($this->incoming[$name]);
if(!@$rule->execute(&$this->incoming[$name])) {
$this->errors[$name] = $rule->getDescription();
}
if(!$was_set && !is_a($rule, 'Checkbox'))
unset($this->incoming[$name]); // special handling for originally unset fields
// it's necessary since call to $rule->execute()
// would create $this->incoming[$name] element
// set to NULL. ( Does it make any sense to
// validate unset values? Nevertheless, original
// state of $this->incoming array is preserved )
// It's done for all rules *except* those which
// are expected to change the value. As for now,
// only Checkbox does so, but later, when we'll
// introduce more modifying rules, it may become
// a problem. Modifying rules are evil.
}
return !count($this->errors);
}
/**
* @return string description of error(s)
*/
function getDescription() {
return implode("<br />\n", $this->errors);
}
}
/**
* uses decorator pattern to check for field existence before passing it to next validator
*/
class Required extends Rule {
/**
* @var Rule $next rule to wrap around
*/
var $next;
/**
* @var string $description description of the error. If value is not set - uses default desc
*/
var $description = 'Required field not set';
/**
* check for field presence and forward request to next validator
* @param mixed $value value to check
* @return bool return value from $next validator if field has been set, false otherwise
*/
function execute($value) {
if( !isset($value) ) return false;
if( is_null($this->next) ) return true; // if no next validator complete processing
$this->description = $this->next->getDescription();
return $this->next->execute($value);
}
/**
* @return string description of error
*/
function getDescription() {
return $this->description;
}
/**
* constructor
* @param Rule $next reference to next validator. Pass null if there is no next validator
* @return void
*/
function __construct(&$next) {
$this->next = &$next;
}
}
/**
* decorator to prevent errors for optional fields
*/
class Optional extends Rule {
/**
* @var Rule $next rule to wrap around
*/
var $next;
/**
* @var string $description description of error (if any)
*/
var $description;
/**
* constructor
* @param Rule $next reference to next validator. Pass null if there is no next validator
* @return void
*/
function __construct(&$next) {
$this->next =& $next;
}
/**
* check for field absence and forward request to next validator if appropriate
* @param mixed $value value to check
* @return bool return value from $next validator if field has been set, true otherwise
*/
function execute($value) {
if( empty($value) || is_null($value) || is_null($this->next) ) return true;
$ret = $this->next->execute($value);
if(!$ret)
$this->description = $this->next->getDescription();
return $ret;
}
/**
* @return string description of error (if any)
*/
function getDescription() {
return $this->description;
}
}
/**
* check if field is alpanumeric
*/
class AlphaNum extends Rule {
/**
* check value against string
* @param string $value value to operate on
* @return bool true if value is alphanumeric, false otherwise
*/
function execute($value) {
return !eregi('[^0-9a-zA-Z]', $value);
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field should contain alphanumeric chars only';
}
}
/**
* process checkboxes and convert them to bool values
*/
class Checkbox extends Rule {
/**
* @param string $value it will be converted as $value = isset($value)
* @return bool always true
*/
function execute(&$value) {
$value = isset($value);
return true;
}
/**
* @return string always empty string
*/
function getDescription() {
return '';
}
}
/**
* rule to check if field contains integer value
*/
class Integer extends Rule {
/**
* @param string $value value to check
* @return bool true if $value is integer, false otherwise
*/
function execute($value) {
return is_numeric($value) && (strval(intval($value)) == $value);
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field should be integer';
}
}
/**
* decorator to check CSV fields
*/
class CSV extends Rule {
/**
* @var Rule $next rule to check elements against
*/
var $next;
/**
* @var array $errors errors array
*/
var $errors = array();
/**
* constructor
* @var Rule $next rule to check elements against
* @return void
*/
function __construct(&$next) {
$this->next =& $next;
}
/**
* checking method
* @var sting $value CSV string
* @return bool true if there were no errors, false if one of the elts of CSV does not pass $next rule
*/
function execute($value) {
if( !is_null($this->next) ) {
$values = explode(',', $value);
foreach($values as $offset => $val) {
if( !$this->next->execute($val) ) {
$this->errors[$offset] = $this->next->getDescription();
}
}
}
return !count($this->errors);
}
/**
* @return string description of error(s)
*/
function getDescription() {
$ret = 'Element validation failed because of:<br />';
foreach($this->errors as $offset => $error) {
$ret .= $error . ' at offset ' . $offset . '<br />';
}
return $ret;
}
}
/**
* Rule to check ISO dates
*/
class ISODate extends Rule {
/**
* @param string $value date to check
* @return bool true if $value is correct date, false otherwise
*/
function execute($value) {
list($year, $month, $day) = explode('-', $value);
return checkdate($month, $day, $year);
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field should contain valid Gregorian date (ISO format: YYYY-MM-DD)';
}
}
/**
* Rule to check input value against list of allowed ones
*/
class Enum extends Rule {
/**
* @var array $values array of allowed values
*/
var $values;
/**
* constructor
* @param array $values array of allowed values
* @return void
*/
function __construct($values) {
$this->values = $values;
}
/**
* checks if $value in $this->values
* @param string $value value to check
* @return bool true on success, false otherwise
*/
function execute($value) {
return in_array($value, $this->values);
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field value is not in allowed set: "' . implode('","', $this->values) . '"';
}
}
/**
* rule to check if given string is indeed valid md5 hash
*/
class MD5_Hash extends Rule {
/**
* checking method
* @param string $value value to check
* @return bool true if value passes check, false otherwise
*/
function execute($value) {
if( strlen($value) != 32 ) return false; // hashes are 32chars strings
return !eregi('[^0-9a-f]', $value);
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field is not a valid md5 hash';
}
}
class Equal extends Rule {
/**
* @var mixed $equal_to value to compare with
*/
var $equal_to;
/**
* constructor
* @param mixed &$equal_to value to compare with
* @return void
*/
function __construct(&$equal_to) {
$this->equal_to = $equal_to;
}
/**
* checking method
* @param mixed &$value value to check
* @return bool true if $value equals to $this->equal_to
*/
function execute($value) {
return $value == $this->equal_to;
}
/**
* @return string description of error
*/
function getDescription() {
return 'Field should have value: ' . $this->equal_to;
}
}