replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
My included in every script file - follow-up -3 (the Validator class)
This question is a sub-post to this question
This post is a follow up for this and this.
"In this particular question I need the reviewers help to judge my Validator class"
Validator.class.php
a class designed to get used by the controllers. it has one purpose "handle client inputs"
<?php
namespace aap;
// flags
define('F_REQUIRED', 1);
define('F_MUSTBESET', 2);
define('F_ESCAPE', 4); // if data is valid it can work only with STRING and EMAIL
define('F_SANITIZE', 8); // if data is valid it can work only with STRING and EMAIL
/*
* define('F_SETIFNOT', 16); // this flag is uselss since passing the $data var
* by reference will make php define it as null anyway
* see my question at :http://stackoverflow.com/questions/41483976
*/
// filter types
define('INT', 1);
define('STRING', 2);
define('FLOAT', 3);
define('DATE', 4);
define('EMAIL', 5);
// define('LONGSTRING', 5);
// error types
define('ER_NOT_SET', 10);
define('ER_EMPTY', 11);
define('ER_DATA', 12);
class Validator
{
protected $options = [];
protected $errors = [];
protected $sysModel;
public function __construct(SystemModel $sysModel, array $options = [])
{
$this->sysModel = $sysModel;
$this->resetOptions();
$this->setOptions($options);
}
public function resetOptions()
{
// defaults
$this->options[INT]['min'] = - 1 * PHP_INT_MAX - 1;
$this->options[INT]['max'] = PHP_INT_MAX;
$this->options[STRING]['length'] = 80;
$this->options[STRING]['regex'] = "/[^a-zA-Z0-9_ أ-ي]/u";
$this->options[STRING]['ischarsinvalid'] = true;
$this->options[FLOAT]['min'] = 0.0;
$this->options[FLOAT]['max'] = INF;
$this->options[DATE]['min'] = "1970年01月01日"; // mysql timestamp range
$this->options[DATE]['max'] = "2038年01月18日"; // mysql timestamp range
$this->options[DATE]['format'] = "Y-m-d"; // 2 formats only are supported Y-m-d H:i:s
}
/*
* function validate is designed to report errors in the object $error array
* @return: the variable $data if it is valid or return false if it is not valid
* @note: passing by reference will make the
*/
public function validate(&$data, $varName, $filterType, $flags = null)
{
if (! isset($data) && ($flags & F_MUSTBESET)) {
$this->errors[$varName]['type'] = ER_NOT_SET;
return false;
}
if (empty($data) && ($flags & F_REQUIRED)) {
$this->errors[$varName]['type'] = ER_EMPTY;
return false;
}
switch ($filterType) {
case INT:
if (isset($data) && ($data < $this->options[INT]['min'] || $data > $this->options[INT]['max'])) {
$this->errors[$varName]['type'] = ER_DATA;
$data = $this->checkEscapeAndSanitize($data, $flags);
return false;
}
return $data;
break;
case STRING:
// @formatter:off
if (! empty($data) && (strlen($data) > $this->options[STRING]['length'] || (preg_match($this->options[STRING]['regex'], $data) && $this->options[STRING]['ischarsinvalid']) || (! preg_match($this->options[STRING]['regex'], $data) && ! $this->options[STRING]['ischarsinvalid']))) {
// @formatter:on
$this->errors[$varName]['type'] = ER_DATA;
$data = $this->checkEscapeAndSanitize($data, $flags);
return false;
}
$data = $this->checkEscapeAndSanitize($data, $flags);
return $data;
break;
case FLOAT:
if (! empty($data) && ($data < $this->options[FLOAT]['min'] || $data > $this->options[FLOAT]['max'])) {
$this->errors[$varName]['type'] = ER_DATA;
$data = $this->checkEscapeAndSanitize($data, $flags);
return false;
}
return $data;
break;
case DATE:
if (! empty($data) && ! Validator::isDate($data, $this->options[DATE]['min'], $this->options[DATE]['max'], $this->options[DATE]['format'])) {
$this->errors[$varName]['type'] = ER_DATA;
$data = $this->checkEscapeAndSanitize($data, $flags);
return false;
}
return $data;
break;
case EMAIL:
if (! empty($data) && filter_var($data, FILTER_VALIDATE_EMAIL) === false) {
$this->errors[$varName]['type'] = ER_DATA;
$data = $this->checkEscapeAndSanitize($data, $flags);
return false;
}
$data = $this->checkEscapeAndSanitize($data, $flags);
return $data;
break;
default:
throw new \InvalidArgumentException("'{$filterType}'filterType value is an unknown filter type!");
break;
}
}
protected function checkEscapeAndSanitize($data, $flags)
{
if ($flags & F_ESCAPE) {
$data = $this->sysModel->escape($data);
}
if ($flags & F_SANITIZE) {
$data = htmlspecialchars($data);
}
return $data;
}
public function flushErrors()
{
$this->errors = [];
}
public function getErrors()
{
return $this->errors;
}
public function setOptions(array $options)
{
foreach ($options as $type => $value) {
switch ($type) {
case INT:
foreach ($value as $option => $optionValue) {
switch ($option) {
case "min":
if (! filter_var($options[INT]['min'], FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException("'{$options[INT]['min']}'[int][min] value is not an integer!");
} else {
$this->options[INT]['min'] = $options[INT]['min'];
}
break;
case "max":
if (! filter_var($options[INT]['max'], FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException("'{$options[INT]['max']}'[int][max] value is not an integer!");
} else {
$this->options[INT]['max'] = $options[INT]['max'];
}
break;
default:
throw new \InvalidArgumentException("'$option' is not a valid option name for int data types!");
break;
}
}
break;
case STRING:
foreach ($value as $option => $optionValue) {
switch ($option) {
case "length":
if (! filter_var($options[STRING]['length'], FILTER_VALIDATE_INT, array(
"options" => array(
"min_range" => 0
)
))) {
throw new \InvalidArgumentException("'{$options[STRING]['length']}'[string][length] value is not a positive integer!");
} else {
$this->options[STRING]['length'] = $options[STRING]['length'];
}
break;
case "regex":
$this->options[STRING]['regex'] = $options[STRING]['regex'];
break;
case "ischarsinvalid":
if (! is_bool($options[STRING]['ischarsinvalid'])) {
throw new \InvalidArgumentException("'{$options[STRING]['ischarsinvalid']}'[string][ischarsinvalid] value is not a bool!");
} else {
$this->options[STRING]['ischarsinvalid'] = $options[STRING]['ischarsinvalid'];
}
break;
default:
throw new \InvalidArgumentException("'$option' is not a valid option name for string data types!");
break;
}
}
break;
case FLOAT:
foreach ($value as $option => $optionValue) {
switch ($option) {
case "min":
if (! is_numeric($options[FLOAT]['min'])) {
throw new \InvalidArgumentException("'{$options[FLOAT]['min']}'[float][min] value is not numeric");
} else {
$this->options[FLOAT]['min'] = $options[FLOAT]['min'];
}
break;
case "max":
if (! is_numeric($options[FLOAT]['max'])) {
throw new \InvalidArgumentException("'{$options[FLOAT]['max']}'[float][max] value is not numeric!");
} else {
$this->options[FLOAT]['max'] = $options[FLOAT]['max'];
}
break;
default:
throw new \InvalidArgumentException("'$option' is not a valid option name for float data types!");
break;
}
}
break;
case DATE:
foreach ($value as $option => $optionValue) {
switch ($option) {
case "min":
if (! Validator::isDate($options[DATE]['min'])) {
throw new \InvalidArgumentException("'{$options[DATE]['min']}'[date][min] value is not a date");
} else {
$this->options[DATE]['min'] = $options[DATE]['min'];
}
break;
case "max":
if (! Validator::isDate($options[DATE]['max'])) {
throw new \InvalidArgumentException("'{$options[DATE]['max']}'[date][max] value is not a date!");
} else {
$this->options[DATE]['max'] = $options[DATE]['max'];
}
break;
case "format":
if ($options[DATE]['format'] != "Y-m-d H:i:s" && $options[DATE]['format'] != "Y-m-d") {
throw new \InvalidArgumentException("'{$options[DATE]['format']}'[date][format] value is not a valid date format");
} else {
$this->options[DATE]['format'] = $options[DATE]['format'];
}
break;
default:
throw new \InvalidArgumentException("'$option' is not a valid option name for date data types!");
break;
}
}
break;
default:
throw new \InvalidArgumentException("'$type' is not a valid type name!");
break;
}
;
}
}
public static function compareDatetime($date1, $date2, $unit)
{
/*
* compare 2 dates and return the difference between date1 and date2 \
* (positive value or nigative)
* @return date1- date2 or false on failure
*/
//
if ($unit != "h" && $unit != "i" && $unit != "s" && $unit != "d") {
return false;
}
;
$date1 = strtotime($date1);
$date2 = strtotime($date2);
if ($date1 === false || $date2 === false) {
return false;
}
;
$diff = $date1 - $date2;
switch ($unit) {
case "d":
return (int) round($diff / (3600 * 24));
case "h":
return (int) round($diff / 3600);
case "i":
return (int) round($diff / 60);
case "s":
return (int) $diff;
}
}
public static function isDate($date, $from = "0001年01月01日", $to = "9999年12月31日", $format = "Y-m-d")
{
// 2 formats only are supported Y-m-d H:i:s Y-m-d
if ($format != "Y-m-d" && $format != "Y-m-d H:i:s") {
return false;
}
if ($format == "Y-m-d") {
if (! $from) {
$from = "0001年01月01日";
}
;
if (! $to) {
$to = "9999年12月31日";
}
;
} else {
if (! $from) {
$from = "0001年01月01日 00:00:00";
}
;
if (! $to) {
$to = "9999年12月31日 00:00:00";
}
;
}
if (($date = \DateTime::createFromFormat($format, $date)) === false) {
return false;
}
if (\DateTime::getLastErrors()['warning_count'] > 0) {
return false;
}
if (($from = \DateTime::createFromFormat($format, $from)) === false) {
return false;
}
if (\DateTime::getLastErrors()['warning_count'] > 0) {
return false;
}
if (($to = \DateTime::createFromFormat($format, $to)) === false) {
return false;
}
if (\DateTime::getLastErrors()['warning_count'] > 0) {
return false;
}
if ($date->format("U") < $from->format("U")) {
return false;
}
if ($date->format("U") > $to->format("U")) {
return false;
}
return true;
}
}
an example of how I plan to use the class
$validator = new Validator($sysModel);
//validating inputs
$options[STRING]['length'] = 20;
$validator->setOptions($options);
$validator->validate($_POST['name'], "name", STRING, F_MUSTBESET );
$options[STRING]['regex'] = "/[^0-9 +]/u";
$options[STRING]['length'] = 15;
$validator->setOptions($options);
$validator->validate($_POST['phone'], "phone", STRING, F_MUSTBESET);
$validator->resetOptions();
$options[STRING]['regex'] = "/.*/u";
$options[STRING]['ischarsinvalid'] = false;
$options[STRING]['length'] = 500;
$validator->setOptions($options);
$validator->validate($_POST['message'], "message", STRING, F_REQUIRED | F_MUSTBESET | F_ESCAPE | F_SANITIZE);
$validator->validate($_POST['email'], "email", EMAIL, F_MUSTBESET | F_ESCAPE | F_SANITIZE);
$validator->resetOptions();
$validator->validate($_POST['title'], "title", STRING, F_MUSTBESET | F_ESCAPE | F_SANITIZE);
$validator->validate($_POST['subject'], "subject", STRING, F_REQUIRED | F_MUSTBESET | F_ESCAPE | F_SANITIZE);
$errorMessage = "";
foreach ($validator->getErrors() as $error => $value) {
switch ($error) {
case "name":
$contact->set("nameInput-class", "incorrect-field");
$errorMessage .= "<li>name field is not correct!</li>\n";
// switch ($value['type']) {case ER_NOT_SET:
break;
case "phone":
$contact->set("phoneInput-class", "incorrect-field");
$errorMessage .= "<li>phone field is not correct!</li>\n";
break;
case "email":
$contact->set("emailInput-class", "incorrect-field");
$errorMessage .= "<li>email field is not correct!</li>\n";
break;
case "subject":
$contact->set("subjectInput-class", "incorrect-field");
$errorMessage .= "<li>invalid input!</li>\n";
break;
case "title":
$contact->set("titleInput-class", "incorrect-field");
$errorMessage .= "<li>title field is not correct!</li>\n";
break;
case "message":
$contact->set("messageInput-class", "incorrect-field");
$errorMessage .= "<li>message field is not correct!</li>\n";
break;
}
}
lang-php