Hallo,
mir ist bewusst, dass das Thema schon älter ist, aber ich möchte es dennoch nochmal aufgreifen, da ich mir auf Basis dieses Themas nun auch einen Validator (ohne Formulargenerierung) gebastelt habe, ich stehe jedoch noch vor einigen konzeptionellen Problemen.
Ich habe folgende Klassen:
Validator - Bündelt die einzelnen "Elemente" (jedes Formularfeld wäre ein Element), die validiert werden sollen
ValidatorElement - Entspricht dem o.g. "Element", bündelt die einzelnen Validationsregeln
AbstractValidator - Basisklasse für einzelne Validationsregeln
BetweenValidator (z.B.) - Spezielle Implementierung einer Validationsregel
Nutzen könne man das ganze z.B. so (vereinfacht, nur eine Regel):
PHP-Code:
<?php
$validator = new Validator($_REQUEST);
$validatorElement = new ValidatorElement('test');
$betweenValidator = new BetweenValidator(1, 10);
$betweenValidator->setMessages(
array(
'NOT_BETWEEN_INCLUSIVE' => 'Gegebener Wert nicht im passenden Bereich ...'
)
);
$validatorElement->addValidator($betweenValidator);
$validator->addElement($validatorElement);
if ($validator->isValid() == false) {
$output = $validator->getErrors();
}
else {
$output = $validatorElement->getValue(); // anstatt $_REQUEST['test']
}
?>
Nun könnte man diese einzelne Regel auch nur mit dem BetweenValidator
prüfen, aber das ist auch nur ein einfaches Beispiel, bei mehreren Validationsregeln und mehreren Feldern wird das ja erst sinnvoll. Nun ist das schon ein ziemlicher Klotz für ein wenig Validation, v.a. die Sprachbehandlung finde ich viel zu umständlich und würde die Nutzung gerne kompakter gestalten.
Schön kompakt wäre es z.B. so, aber einerseits ist das vllt. nicht allzu schön und die Sprachbehandlung fehlt auch noch:
PHP-Code:
<?php
$validatorElement = new ValidatorElement('test');
$validatorElement->addValidators(
new BetweenValidator(1, 10),
new XYZValidator('Blubb'),
// ...
);
$validator = new Validator($_REQUEST);
$validator->addElements($validatorElement, ..., ...);
if ($validator->isValid() == false) {
$output = $validator->getErrors();
}
else {
$output = $validatorElement->getValue(); // anstatt $_REQUEST['test']
}
?>
Hat jemand eine Idee, wie man das halbwegs kompakt, aber dennoch möglichst flexibel gestalten könnte?
Ansonsten würde ich auch gerne einmal wissen, ob sonstige konzeptionelle Verbesserung möglich wären.
Dank und Grüße
MaMo
Anhang: Source Code PHP-Code:
<?php
/**
* Validator class that bundles the ValidatorElement objects.
*
* Validation classes base on a concept discussed at php.de, see link.
*
* @package Core
* @subpackage Security
* @author Matthias Mohr
* @author Andreas Wilhelm
* @copyright Copyright (c) 2004-2010, Viscacha.org
* @since 1.0
* @see http://www.php.de/software-design/50128-formular-validierung.html
*/
class Validator {
protected $data = array();
protected $elements = array();
protected $errors = array();
/**
* @param array Array with data to validate
*/
public function __construct($data) {
$this->data = $data;
}
/**
* Adds an element to the Validator.
*
* If you specify an Element with a name that was used by another element before, this element
* won't overwrite the element specified before.
*
* @param ValidatorElement
*/
public function addElement(ValidatorElement $element) {
$name = $element->getName();
if (isset($this->data[$name]) == true) {
$element->setValue($this->data[$name]);
}
// check if entry exists
if(!isset($this->elements[$name])) {
$this->elements[$name] = $element;
}
else {
// Write a message to the log file as this can lead to unapplied Validators (risky)!
ErrorHandling::getDebug()->addText(
"Validation: You specified an element multiple times, this can be a security hole!"
);
}
}
/**
* Checks if any errors accured in this form.
*
* @return boolean
*/
public function isValid() {
$isValid = true;
foreach($this->elements as $element) {
if($element->isValid() == false) {
$isValid = false;
$this->errors[$element->getName()] = $element->getErrors();
}
}
return $isValid;
}
/**
* Returns the errors accured in a validator.
*
* @return array
*/
public function getErrors() {
return $this->errors;
}
}
?>
PHP-Code:
<?php
/**
* ValidatorElement class that bundles the validation rules and filters for this element.
*
* @package Core
* @subpackage Security
* @author Andreas Wilhelm
* @author Matthias Mohr
* @copyright Copyright (c) 2004-2010, Viscacha.org
* @since 1.0
* @see http://www.php.de/software-design/50128-formular-validierung.html
*/
class ValidatorElement {
protected $name;
protected $value;
protected $validators;
protected $errors;
/**
* @param string Element name
*/
public function __construct($name, $value = null) {
$this->validators = array();
$this->errors = array();
$this->name = $name;
$this->value = $value;
}
/**
* Adds a validator to an element.
*
* @param AbstractValidator
* @param boolean Make this Validator optional (true) or not (false, default).
* @todo Find a better method for the in_array check (maybe a uniqueId method for validators)
*/
public function addValidator(AbstractValidator $validator, $optional = false) {
// Don't add a validator with the same data multiple times
if(in_array($validator, $this->validators) == false) {
$validator->setOptional($optional);
$this->validators[] = $validator;
}
}
/**
* Returns the name of an element.
*
* @return string
*/
public function getName() {
return $this->name;
}
/**
* Returns the value of an element.
*
* @return mixed
*/
public function getValue() {
return $this->value;
}
/**
* Assigns a value to an element.
*
* @param mixed
*/
public function setValue($value) {
$this->value = $value;
}
/**
* Checks if any errrors accured in this element.
*
* @return Boolean
*/
public function isValid() {
$isValid = true;
foreach($this->validators as $validator) {
if($validator->isValid($this->value) == false) {
$isValid = false;
$this->errors = array_merge($this->errors, $validator->getErrors());
}
}
return $isValid;
}
/**
* Returns the errors occured in an element.
*
* @access public
* @return Array
*/
public function getErrors() {
return $this->errors;
}
}
?>
PHP-Code:
<?php
/**
* Abstract validator that has to be extended by all Validator rule classes.
*
* @package Core
* @subpackage Security
* @author Matthias Mohr
* @copyright Copyright (c) 2004-2010, Viscacha.org
* @since 1.0
* @see http://www.php.de/software-design/50128-formular-validierung.html
* @abstract
*/
abstract class AbstractValidator {
private $errors;
private $messages;
protected $optional;
/**
* Constructs a new validator rule.
*/
public function __construct() {
$this->reset();
$this->messages = array();
}
/**
* Checks the specified parameter against the rules.
*
* @param mixed
* @return boolean returns true if valid, false if invalid.
*/
public abstract function isValid($value);
/**
* Sets whether this validator is optional or not.
*
* @param boolean true = optional, false = required
*/
public function setOptional($optional) {
$this->optional = $optional;
}
/**
* Returns the error codes as array.
*
* @return array
*/
public function getErrors() {
return $this->errors;
}
/**
* Returns the number of errors.
*
* @return int
*/
public function countErrors() {
return count($this->errors);
}
/**
* Sets the human readable error messages.
*
* You have to specify an array with the error codes as keys and the messages itself as values.
*
* @param array
*/
public function setMessages(array $messages) {
$this->messages = $messages;
}
/**
* Returns the error messages as array.
*
* The keys are the error codes and the values are the human readable messages.
* You have to specify the messages before with AbstractValidator::setMessages().
* If no error message is found for an error code the error code will be returned as value.
*
* @return array
*/
public function getMessages() {
$messages = array();
foreach ($this->errors as $code) {
if (isset($this->messages[$code]) == true) {
$messages[$code] = $this->messages[$code];
}
else {
$messages[$code] = $code;
}
}
return $messages;
}
/**
* Adds an error message to the error array.
*
* @param string Error code
*/
protected function setError($error) {
$this->errors[] = $error;
}
/**
* Resets the validator to be able to validate another value.
*/
protected function reset() {
$this->errors = array();
}
}
?>
PHP-Code:
<?php
/**
* Validates that a number is between two numbers.
*
* @package Core
* @subpackage Security
* @author Matthias Mohr
* @copyright Copyright (c) 2004-2010, Viscacha.org
* @since 1.0
* @see http://www.php.de/software-design/50128-formular-validierung.html
*/
class BetweenValidator extends AbstractValidator {
const NOT_BETWEEN_EXCLUSIVE = 'NOT_BETWEEN_EXCLUSIVE';
const NOT_BETWEEN_INCLUSIVE = 'NOT_BETWEEN_INCLUSIVE';
protected $min;
protected $max;
protected $inclusive;
public function __construct($min, $max, $inclusive = true) {
parent::__construct();
$this->min = $min;
$this->max = $max;
$this->inclusive = $inclusive;
}
public function isValid($value) {
$this->reset();
if ($this->optional == true && empty($value) == true) {
return true;
}
if ($this->inclusive == true) {
if (Numbers::isDecimal($value) == true && $this->min >= $value && $value <= $this->max) {
return true;
}
else {
$this->setError(self::NOT_BETWEEN_INCLUSIVE);
return false;
}
}
else {
if (Numbers::isDecimal($value) == true && $this->min > $value && $value < $this->max) {
return true;
}
else {
$this->setError(self::NOT_BETWEEN_EXCLUSIVE);
return false;
}
}
}
}
?>