How to use reusable validation in a ValueObject
Asked Answered
R

3

9

I'm trying to get my head around combining some techniques.

It seems good practice to never make it possible to create a ValueObject that is not valid. The ValueObject constructor therefor should fail whenever the provided content is not good enough to create a valid ValueObject. In the examples I have, an EmailAddress object can only be created when there is a value present. So far, so good.

Validating the value of the provided emailaddress, that's where I begin to doubt the principles. I have four examples, but I can't tell which one should be considered the best practice.

Example 1 is the easy one: simply a construct function, a required parameter "value", and a separate function validate to keep the code clean. All the validation code stays inside the class, and will never be available to the outside world. The class has only one purpose: store the emailaddress, and make sure it will never be an invalid one. But the code will never be reusable - I create an object with it, but that's all.

public function __construct ($value)
{
    if ( $this->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

protected function validate ($value)
{
    return is_string($value); // Wrong function, just an example
}

Example 2 makes the validate function a static function. The function will never change the state of the class, so it is a correct use of the static keyword, and the code in it will never be able to change anything to any instance created from the class embedding the static function. But if I want to reuse the code, I can call the static function. Still, this feels dirty to me.

public function __construct ($value)
{
    if ( $self::validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

public static function validate ($value)
{
    return is_string($value); // Wrong function, just an example
}

Example 3 introduces another class, hardcoded inside the body of my object. The other class is a validation class, containing the validation code, and creates thus a class that can be used whenever and wherever I need a validation class. The class itself is hardcoded, which also means that I create a dependency on that validation class, which should be always nearby, and is not injected through dependency injection. One could say that having a validator hard coded is as bad as having the complete code embedded in the object, but on the other hand: DI is important, and this way one has to create a new class (extending, or simply rewriting) to simply change the dependency.

public function __construct ($value)
{
    if ( $this->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

protected function validate ($value)
{
    $validator = new \Validator();
    return $validator->validate($value);
}

Example 4 uses the validator class again, but puts it in the constructor. My ValueObject thus needs a validator class already present and created, before creating the class, but it is possible to easily overwrite the validator. But how good is it for a simple ValueObject class to have such a dependency in the constructor, as the only thing really important is the value, it should not be my concern to know how and where to handle if the email is correct, and providing a correct validator.

public function __construct ($value, \Validator $validator)
{
    if ( $validator->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

The last example I started thinking about, is providing a default validator, and meanwhile make it possible to inject through DI an overwrite for the validator in the constructor. But I started doubting how good a simple ValueObject is when you overwrite the most important part: the validation.

So, anyone has an answer which way one should best write this class, that is correct for something as easy as an emailaddress, or something more complex like a barcode or a visa card or whatever one may think about, and doesn't violate DDD, DI, OOP, DRY, wrong use of static, and so on...

The complete code:

class EmailAddress implements \ValueObject
{

protected $value = null;

// --- --- --- Example 1

public function __construct ($value)
{
    if ( $this->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

protected function validate ($value)
{
    return is_string($value); // Wrong function, just an example
}

// --- --- --- Example 2

public function __construct ($value)
{
    if ( $self::validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

public static function validate ($value)
{
    return is_string($value); // Wrong function, just an example
}

// --- --- --- Example 3

public function __construct ($value)
{
    if ( $this->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

protected function validate ($value)
{
    $validator = new \Validator();
    return $validator->validate($value);
}

// --- --- --- Example 4

public function __construct ($value, \Validator $validator)
{
    if ( $validator->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}

}
Recover answered 21/1, 2014 at 20:58 Comment(3)
There is a similar question if you're interestedHelterskelter
if you use an external validator then your value object it's not a value object anymore. you already know what the validation is and always will beLope
why do you think you will need to reuse the validation code from outside that class? The validator is the Email class itself already. If you find yourself having to use the "validation code" from outside that class then you are likely doing something wrong.Lope
T
1

Example 4!

Why? Because it's testable, plain and simple.

Depending on what your validator actually does (in some circumstances your validator may rely on an API call or a call to a database) the injectable validator is completely testable via mocks. All of the other's are either impossible to test under the circumstances I just mentioned, or incredibly hard to test.

EDIT: For those wondering how the dependency injection method helps with testing then consider the CommentValidator class below that utilises a standard Akismet spam checking library.

class CommentValidator {
    public function checkLength($text) {
        // check for text greater than 140 chars
        return (isset($text{140})) ? false : true;
    }

    public function checkSpam($author, $email, $text, $link) {
        // Load array with comment data.
        $comment = array(
                        'author' => $author,
                        'email' => $email,
                        'website' => 'http://www.example.com/',
                        'body' => $text,
                        'permalink' => $link
                );

        // Instantiate an instance of the class.
        $akismet = new Akismet('http://www.your-domain.com/', 'API_KEY', $comment);

        // Test for errors.
        if($akismet->errorsExist()) { // Returns true if any errors exist.
            if($akismet->isError('AKISMET_INVALID_KEY')) {
                    return true;
            } elseif($akismet->isError('AKISMET_RESPONSE_FAILED')) {
                    return true;
            } elseif($akismet->isError('AKISMET_SERVER_NOT_FOUND')) {
                    return true;
            }
        } else {
            // No errors, check for spam.
            if ($akismet->isSpam()) {
                    return true;
            } else {
                    return false;
            }
        }
    }
}

And now below, when you're setting up your unit tests we have a CommentValidatorMock class that we use instead, we have setters to manually change the 2 output bools we can have, and we have the 2 functions from above mock'd up to output whatever we want without having to go through the Akismet API.

class CommentValidatorMock {
    public $lengthReturn = true;
    public $spamReturn = false;

    public function checkLength($text) {
        return $this->lengthReturn;
    }

    public function checkSpam($author, $email, $text, $link) {
        return $this->spamReturn;
    }

    public function setSpamReturn($val) {
        $this->spamReturn = $val;
    }

    public function setLengthReturn($val) {
        $this->lengthReturn = $val;
    }
}

If you're serious about unit testing then you need to use DI.

Thirza answered 22/1, 2014 at 16:10 Comment(6)
And would there be anything wrong with using a default validator in example 4, whenever I submit no validator with the constructor?Recover
I wouldn't recommend doing that. I would have another check in there that throws an Exception if a validator wasn't passed instead and have something catch and handle that. I would recommend sticking completely with the DI model and not bolting other bits and pieces on.Thirza
But if I write the code for the validation completely in the valueobject, it would be okay to do that? In that case I can still test if a wrong emailaddress is throwing an error - and write my testcases like so. But if I use an external class to do the validation, I'm left with only one option: always provide even the default validator through the constructor, and probably always create my valueobjects through a factory?Recover
There's no reason why this option means you have to create your objects via a factory, that's a design decision on your part. However, yes, you always will have to provide a validator, although I can't see any reason why you would want to have user input that wasn't validated... And if you're running into the issue of having to provide a validator for user data that is returned from the database then you maybe shouldn't have the validator in the constructor if you're using your objects in that way.Thirza
I don't especially need the validator, but I do need validation the moment I instantiate the object - which happens the moment I first create the object, which will for sure happen the very first time before giving it to the orm. Whenever I create the object, it should be "valid", but I keep asking myself if that validation can be put outside the valueobject-class - and in the fourth way, I need a DI Container or a simple factory, 'cause a setValidator method would make it possible to create an invalid ValueObject. But I'm not sure, some confusion going on over this - therefor the question.Recover
value objects aren't supposed to use factories, they have 1 implementation only and it's well defined and constantLope
P
1

The first instinct is usually the best. You should use the first option. EmailAddress is a value object. It can be reused in other value objects or entities. I don't understand why you think it's not reusable. You can have a "shared library" of these common value objects used in other bounded contexts. Just be careful what you put in there. They would need to be truly generic if that's even conceptually possible.

Peal answered 25/2, 2016 at 20:42 Comment(0)
M
0

I think if you use separate validation methods or move the validators to separate class will be butter and prevent DRY


    class EmailAddress{
      protected $value;
      public function __construct ($value)
      {
        $this->value = \validateEmailAddress($value);
      }
    }

    function validateEmailaddress(string $value) : string  
    {
      if(!is_string($value)){
        throw new \ValidationException('This is not an emailaddress.');
      } // Wrong function, just an example
      return $value;
    }

    //OR for strict OOP people
    final class VOValidator{
      private function __construct(){}
      public static function validateEmailaddress(string $input): string{...}
    }


    //I will prefer even go far and use Either from (FP monads) 

    interface ValueObejctError {}
    class InvalidEmail implements ValueObjectError {}

    function validateEmailaddress(string $input): Either { 
    // it will be better if php supported generic so using Either<InvalidaEmail, string> is more readable but unfortunately php has no generic types, maybe in future
      return is_string($input) 
        ? new Right($input)
        : new Left(new InvalidEmail());
    }

Madonnamadora answered 7/9, 2020 at 0:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.