Validator extended to handle domain objects / entities I started improving my validator library even more, and I think I'm at the last final element (domain object / entity validations) before I can gladly say that my Validator library is completely done and it supports all of it.

Here you can see that my validator can already deal with regular variable inputs or non-entity inputs (certain forms for example).

I started figuring out how I could extend my validator to create domain object validators and here is my attempt.

Could anybody review it and see if you find any room for improvements and give constructive feedback?

User

class User
{
    public $name;
    public $email;
    public $age;
}


UserValidator

class UserValidator extends Validator
{
    public function __construct()
    {
        $rules['name']  = [new MaxChars(20), new Alpha(true)];
        $rules['email'] = [new Email()];
        $rules['age']   = [new Numeric(), new Between(13, 17)];

        $this-&gt;setRule('name',  $rules['name']);
        $this-&gt;setRule('email', $rules['email']);
        $this-&gt;setRule('age',   $rules['age']);
    }

    public function validate(User $user)
    {
        $this-&gt;errors = [];

        parent::validate('name',  $user-&gt;name);
        parent::validate('email', $user-&gt;email);
        parent::validate('age',   $user-&gt;age);

        return empty($this-&gt;errors);
    }
}


Validator

class Validator
{
    protected $rules  = [];
    protected $errors = [];

    public function getErrors($rulename = null)
    {
        if ($rulename) {
            return isset($this-&gt;errors[$rulename]) ? $this-&gt;errors[$rulename] : [];
        }

        return $this-&gt;errors;
    }

    public function setRule($name, Array $rules, $required = true)
    {
        $this-&gt;rules[$name]['rules'] = $rules;

        switch (true) {
            case ($required === true):
                $this-&gt;rules[$name]['required'] = 'Must not be null or empty.';
                break;
            case ($required === false):
                $this-&gt;rules[$name]['required'] = false;
                break;
            default:
                $this-&gt;rules[$name]['required'] = $required;
                break;
        }
    }

    public function validate($rulename, $input)
    {
        if ($this-&gt;rules[$rulename]['required']) {
            if (empty($input)) {
                $this-&gt;errors[$rulename][] = $this-&gt;rules[$rulename]['required'];
            }

            foreach ($this-&gt;rules[$rulename]['rules'] as $rule) {
                if (!$rule-&gt;check($input)) {
                    $this-&gt;errors[$rulename][] = $rule-&gt;getError();
                }
            }
        } elseif (!empty($input)) {
            foreach ($this-&gt;rules[$rulename]['rules'] as $rule) {
                if (!$rule-&gt;check($input)) {
                    $this-&gt;errors[$rulename][] = $rule-&gt;getError();
                }
            }
        }

        return isset($this-&gt;errors[$rulename]);
    }
}


Example

$user        = new User();
$user-&gt;name  = 'John Doe';
$user-&gt;email = 'john@doe.com';
$user-&gt;age   = 13;

$uv = new UserValidator();

$uv-&gt;validate($user)); //true
$uv-&gt;getErrors());     //empty array
I disagree with @Madara Uchiha on a several points.


If you couple business rule validations to the domain object, you cannot apply different business rules to the same domain object.

Never assume you will only have one set of business rules. By creating validators outside the domain object, you give yourself the flexibility of creating whole different swaths of business rules without touching the domain layer.

Basically, by the time you go to save a domain object to a database or other repository it should be completely valid from a business rule AND storage perspective. This allows you to communicate problems to the user in a manor that allows them to correct their mistake. If you let a SQL error propagate up the call stack and result in a 500 Internal Server Error, you as a programmer have not done your due diligence when validating data.

Now I could see a case for separating the storage validations from the business rules. Maybe you start out persisting your domain objects in a database, but later you move to a RESTfull web service. The web service may have different basic data validations, which you can then swap out for a different validator while keeping your existing business rule validations.
I disagree that this solution doesn't scale. Quite the opposite actually. It scales very well. Mashing your validations into the domain object is what doesn't scale. Changing the storage mechanism or needing to apply different categories of business rules gets complicated when your domain objects validate themselves. Domain objects should be mere boxes containing data. The decision of whether that data is correct is wholly out of scope for the domain object, in my opinion.
The storage manager should have absolutely no validations. It should accept a domain object, attempt to persist it and throw an exception if anything goes wrong.


Really, all of my objections are related to this tenet of software engineering, and especially object oriented programming: Do one thing, and do it well.

Putting validations into your domain objects means they are serving a dual purpose: Hold data from a storage medium AND ensuring it is valid. Do one thing and do it well. The domain object should hold data. The validation layer should ensure things are valid.

Putting validations into your storage manager means it is serving a dual purpose in the same manor. You should be able to swap out one layer for another with minimal refactoring. If you have to copy and paste of rewrite lots of code, then your application needs additional layers with looser coupling.