Created
November 23, 2021 20:43
-
-
Save thepsion5/4cdcb2de3619639eb446a1588d21f1bb to your computer and use it in GitHub Desktop.
Example of A Flexible Validation Solution Using Classes For Validation Rules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Validates a value based on its length as a string | |
* | |
* ```php | |
* $toBeValidated = 17; | |
* $validationParameters = [ | |
* 'min' => 10 | |
* ]; | |
* $errorMessage = ''; | |
* | |
* $rule = new MinLengthValidationRule(); | |
* $isValid = $rule->isValid($toBeValidated, $validationParameters, $errorMessage); | |
* | |
* // true | |
* echo $isValid; | |
* ``` | |
*/ | |
class MinLengthValidationRule implements ValidationRule | |
{ | |
/** | |
* The name of the rule | |
*/ | |
private CONST RULE_NAME = 'min_length'; | |
/** | |
* The message used when validation fails, with replacements for the field | |
* name and parameters | |
*/ | |
private CONST FAILURE_MESSAGE = '%s must have a length of %d or greater.'; | |
/** | |
* @return string The name of this rule as a string | |
*/ | |
public function getRuleName(): string | |
{ | |
return $this::RULE_NAME; | |
} | |
/** | |
* Validates the provided value by checking its string length against a | |
* specified minimum | |
* | |
* @param mixed $value The value to be validated | |
* @param array $ruleParameters | |
* An associative array containing: | |
* - min: An integer with the minimum required length | |
* @param string $errorMessage Error message string populated if validation fails | |
* @return bool True if the provided value is valid, false otherwise | |
* @throws LogicException If the provided "min" rule parameter is invalid | |
*/ | |
public function isValid($value, array $ruleParameters, string &$errorMessage): bool | |
{ | |
$minLength = isset($ruleParameters['min']) ? (int) $ruleParameters['min'] : 0; | |
if (!array_key_exists('min', $ruleParameters)) { | |
throw new LogicException('The "min" parameter is required and must be an integer greater than zero'); | |
} | |
$valid = strlen((string) $value) >= $minLength; | |
if (!$valid) { | |
$errorMessage = sprintf($this::FAILURE_MESSAGE, '%s', $minLength); | |
} | |
return $valid; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Defines a rule for validating a single value including an arbitrary set of parameters | |
*/ | |
interface ValidationRule | |
{ | |
/** | |
* The name of the validation rule | |
* | |
* @return string | |
*/ | |
public function getRuleName(): string; | |
/** | |
* Checks whether the provided value is valid based on the given parameters, | |
* if applicable. If the value does not pass validation $errorMessage is | |
* populated with an error message containing "%s" as a replacement for the | |
* field name | |
* | |
* NOTE: The choice to use a parameter to hold the error message is an unusual | |
* one, but I wanted to keep this class entirely stateless and felt this was | |
* the best way | |
* | |
* @param mixed $value Some value to be validated | |
* @param array $ruleParameters Named parameters required for the validation, | |
* if any | |
* @param string $errorMessage Empty string passed to the validation method, | |
* only populated if validation fails | |
* @return bool True if $value is valid based on the rule and supplied | |
* parameters, false otherwise | |
*/ | |
public function isValid($value, array $ruleParameters, string &$errorMessage): bool; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Validates some payload based on a given set of rules and stores any errors | |
* associated with the last payload | |
* | |
* ```php | |
* $payload = [ | |
* 'first_name' => 'Sean', | |
* 'middle_name' => '', | |
* 'last_name' => 'Mumford', | |
* ]; | |
* $ruleset = [ | |
* 'first_name' => [ | |
* 'min_length' => ['min' => 2] | |
* ], | |
* 'last_name' => [ | |
* 'min_length' => ['min' => 2] | |
* ], | |
* ]; | |
* | |
* $validator = new Validator(); | |
* $validator->addRule(new MinLengthValidationRule); | |
* | |
* $isValid = $validator->validate($ruleset, $payload); | |
* if (!$isValid) { | |
* $_SESSION['message'] = 'Please correct the error with your submission before proceeding.'; | |
* $_SESSION['errors'] = $validator->getErrors(); | |
* header('Location: form.php'); | |
* die(); | |
* } | |
* ``` | |
*/ | |
class Validator | |
{ | |
/** | |
* Current validation rules | |
* | |
* @var ValidationRule[] | |
*/ | |
private $rules = []; | |
/** | |
* Set of errors, grouped by field name and then rule name | |
* | |
* @var array[] | |
*/ | |
private $errors = []; | |
/** | |
* Sets a rule, replacing any previously set rule with that name | |
* | |
* @param ValidationRule $rule | |
*/ | |
public function setRule(ValidationRule $rule): void | |
{ | |
$ruleName = $rule->getRuleName(); | |
$this->rules[$ruleName] = $rule; | |
} | |
/** | |
* Checks to see whether there is already an existing rule with the same | |
* set with this validator | |
* | |
* @param string|ValidationRule $rule A rule name of ValidationRule instance | |
* to check | |
* @return bool true if a rule of the same name already exists, false | |
* otherwise | |
*/ | |
public function hasRule($rule): bool | |
{ | |
$ruleName = ($rule instanceof ValidationRule) ? $rule->getRuleName() : $rule; | |
return array_key_exists($ruleName, $this->rules); | |
} | |
/** | |
* Adds an error to the list of errors for a particular field for a particular | |
* rule violation | |
* | |
* @param string $paramName The name of the parameter to which the error applies | |
* @param string $ruleName The rule to which the error applies | |
* @param string $errorMessage The text of the error message | |
*/ | |
public function addError(string $paramName, string $ruleName, string $errorMessage) | |
{ | |
if (!isset($this->errors[$paramName][$ruleName])) { | |
$this->errors[$paramName][$ruleName] = []; | |
} | |
$this->errors[$paramName][$ruleName][] = $errorMessage; | |
} | |
/** | |
* Removes all currently-stored errors | |
*/ | |
public function clearErrors(): void | |
{ | |
$this->errors = []; | |
} | |
/** | |
* Returns whether there are currently any errors stored | |
* | |
* @return bool | |
*/ | |
public function hasErrors(): bool | |
{ | |
return count($this->errors) > 0; | |
} | |
/** | |
* Validates an array of data based on the provided ruleset | |
* | |
* @param array $ruleset An associative array with the field name as the key | |
* and an array of rules and their parameters as a value: | |
* ```php | |
* $ruleset = [ | |
* 'first_name' => [ | |
* 'min_length' => ['min' => 2] | |
* ], | |
* 'last_name' => [ | |
* 'min_length' => ['min' => 2] | |
* ], | |
* ]; | |
* ``` | |
* | |
* @param array $payload An associative array of values to validate | |
* @return bool True if there are no errors associated with the provided values and ruleset, false otherwise | |
*/ | |
public function validate(array $ruleset, array $payload): bool | |
{ | |
$this->clearErrors(); | |
foreach($ruleset as $fieldName => $fieldRules) { | |
$fieldValue = $payload[$fieldName] ?? null; | |
$this->validateField($fieldName, $fieldValue, $fieldRules); | |
} | |
return $this->hasErrors(); | |
} | |
/** | |
* Validates a single field based on the provided validation rules | |
* | |
* @param string $fieldName The name of the field to validate | |
* @param mixed $fieldValue The value of the field | |
* @param array $fieldRules An associative array containing a rule name as the | |
* key and an array of rule parameters | |
*/ | |
private function validateField(string $fieldName, $fieldValue, array $fieldRules) | |
{ | |
foreach($fieldRules as $ruleName => $ruleParams) { | |
if (!array_key_exists($ruleName, $this->rules)) { | |
throw new LogicException("Unknown rule '$ruleName'."); | |
} | |
$errorMessage = ''; | |
$valid = $this->rules[$ruleName]->validate($fieldValue, $ruleParams, $errorMessage); | |
if(!$valid) { | |
$this->addError($fieldName, $ruleName, $errorMessage); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment