patternphpMinor
Validator refactored to be OO
Viewed 0 times
validatorrefactoredstackoverflow
Problem
Based on my previous post I completely re-factored my code to make it object oriented.
Anything I could improve when it comes to:
Any other suggestions are also welcome!
Validator class:
One of the rule classes:
```
class Between implements IRule
{
private $errMsg = 'Must be between %s and %s.';
private $inclusive;
private $max;
private $min;
public function __construct($min, $max, $inclusive = true, $errMsg = null)
{
$this->min = $min;
$this->max = $max;
$this->inclusive = $inclusive;
if ($errMsg !== null) {
$this->errMsg = $errMsg;
}
}
public function check($input)
{
if ($this->inclusive && $
Anything I could improve when it comes to:
- Object oriented code;
- Efficiency;
- Readability.
Any other suggestions are also welcome!
Validator class:
class Validator
{
private $rules = [];
private $errors = [];
public function getErrors($rulename = null)
{
if ($rulename) {
return isset($this->errors[$rulename]) ? $this->errors[$rulename] : null;
}
return $this->errors;
}
# rules will be checked against in same order as specified
public function setRule($name, Array $rules)
{
if (!array_key_exists($name, $this->rules)) {
foreach ($rules as $rule) {
if (!$rule instanceof IRule) {
throw new Exception('Array must only contain instances of IRule.');
}
}
$this->rules[$name] = $rules;
} else {
throw new Exception(sprintf('Rule %s already exists.', $name));
}
}
public function validate($rulename, $input)
{
foreach ($this->rules[$rulename] as $rule) {
try {
$rule->check($input);
} catch (Exception $e) {
$this->errors[$rulename][] = $e->getMessage();
}
}
if (isset($this->errors[$rulename])) {
return false;
}
return true;
}
}One of the rule classes:
```
class Between implements IRule
{
private $errMsg = 'Must be between %s and %s.';
private $inclusive;
private $max;
private $min;
public function __construct($min, $max, $inclusive = true, $errMsg = null)
{
$this->min = $min;
$this->max = $max;
$this->inclusive = $inclusive;
if ($errMsg !== null) {
$this->errMsg = $errMsg;
}
}
public function check($input)
{
if ($this->inclusive && $
Solution
This is a really solid start. I only have a couple of suggestions.
-
Instead of defining the rules outside of the
-
Create public methods specifically for each kind of validator.
Some pseudo code to get the main idea:
validator.php
rule_builder.php
And your specific validator:
To use it:
This way all of your rule classes are hidden. All of the validation rules are portable because they are created inside a model specific class, and it's easy to use an IDE's auto complete feature to discover which rules are available since the RuleBuilder class has strongly typed methods encapsulating each rule.
On top of that, the IRule interface just accepts a value and not a model, making each rule unit testable to ensure your validation library is functioning properly.
And for those who do some .NET/C# development, this pattern probably looks familiar if you've used the FluentValidation NuGet package for Visual Studio. Say what you want about .NET development, but there are some gems out there. I do like the pattern that FluentValidation uses.
-
Instead of defining the rules outside of the
Validator class, it might be a good idea to sub class Validator and set the rules inside the constructor.-
Create public methods specifically for each kind of validator.
Some pseudo code to get the main idea:
validator.php
class Validator
{
private $rule_collection = array();
public function __construct()
{
}
public function ruleFor($property)
{
if (!isset($this->rule_collection[$property]))
$this->rule_collection[$property] = new RuleBuilder($property);
return $this->rule_collection[$property];
}
public function validate($model)
{
$valid = true;
foreach ($this->rule_collection as $property => $rule_builder)
{
foreach ($rule_builder->rules as $rule)
{
if (!$rule->validate($model->$property))
$valid = false;
}
}
return $valid;
}
}rule_builder.php
interface IRule
{
public validate($value);
public get_message();
}
class RuleBuilder
{
private $property;
public $rules = array();
public function __construct($property)
{
$this->property = $property;
}
public function isRequired()
{
return $this->addRule(new RequiredRule());
}
public function length($min, $max = -1)
{
return $this->addRule(new LengthRule($min, $max));
}
public function addRule(IRule $rule)
{
$this->rules[] = $rule;
return $this;
}
}And your specific validator:
class UserValidator extends Validator
{
public function __construct()
{
$this->ruleFor('name')
->isRequired()
->length(20);
}
}To use it:
$user = new User();
$user->name = "Foo";
$validator = new UserValidator();
if ($validator->validate($user))
// save
else
// redraw form and show errorsThis way all of your rule classes are hidden. All of the validation rules are portable because they are created inside a model specific class, and it's easy to use an IDE's auto complete feature to discover which rules are available since the RuleBuilder class has strongly typed methods encapsulating each rule.
On top of that, the IRule interface just accepts a value and not a model, making each rule unit testable to ensure your validation library is functioning properly.
And for those who do some .NET/C# development, this pattern probably looks familiar if you've used the FluentValidation NuGet package for Visual Studio. Say what you want about .NET development, but there are some gems out there. I do like the pattern that FluentValidation uses.
Code Snippets
class Validator
{
private $rule_collection = array();
public function __construct()
{
}
public function ruleFor($property)
{
if (!isset($this->rule_collection[$property]))
$this->rule_collection[$property] = new RuleBuilder($property);
return $this->rule_collection[$property];
}
public function validate($model)
{
$valid = true;
foreach ($this->rule_collection as $property => $rule_builder)
{
foreach ($rule_builder->rules as $rule)
{
if (!$rule->validate($model->$property))
$valid = false;
}
}
return $valid;
}
}interface IRule
{
public validate($value);
public get_message();
}
class RuleBuilder
{
private $property;
public $rules = array();
public function __construct($property)
{
$this->property = $property;
}
public function isRequired()
{
return $this->addRule(new RequiredRule());
}
public function length($min, $max = -1)
{
return $this->addRule(new LengthRule($min, $max));
}
public function addRule(IRule $rule)
{
$this->rules[] = $rule;
return $this;
}
}class UserValidator extends Validator
{
public function __construct()
{
$this->ruleFor('name')
->isRequired()
->length(20);
}
}$user = new User();
$user->name = "Foo";
$validator = new UserValidator();
if ($validator->validate($user))
// save
else
// redraw form and show errorsContext
StackExchange Code Review Q#54799, answer score: 4
Revisions (0)
No revisions yet.