1

I'm wondering how to in the best way create value objects.

For example:

I have value object Password

class Password
{
   private string $password;

   public function __construct(string $password)
   {
      $this->password = $password;
   }

   public function __toString(): string
   {
      return $this->password;
   }

}

And now I wonder how to correctly validate this.

VO should always have the correct value. Should I validate the passed value due to creating an object?

For example:

class Password
{
   private string $password;

   public function __construct(string $password)
   {
      $this->validate($password);

      $this->password = $password;
   }

   public function __toString(): string
   {
      return $this->password;
   }

   private function validate(string $password): void
   {
       if(!validation_rule)
       {
         throw new InvalidPasswordException();
       }
   }
}

Or should I use the factory?

class PasswordFactory implements PasswordFactoryInterface
{
    private PasswordValidatorInterface $validator;

    public function __construct(PasswordValidatorInterface $validator)
    {
        $this->validator = $validator;
    }

    public function createFromString(string $password): Password
    {
        $this->validator->validate($password);

        return new Password(password_hash($password, PASSWORD_BCRYPT_DEFAULT_COST));
    }
}

My conclusions:

Validation due construction:

Advantages

  1. I will not create an invalid object.
  2. Less code.

Disadvantages

  1. I cannot use dependency injection for external validators.

Validation in factories:

Advantages

  1. I can use external libs for validations with Dependency Injection.
  2. Imho easy to test.
  3. Hermetized operation. If I have to change the constructor I will change invocations only in the factory.

Disadvantages

  1. I allow to create an invalid object if someone won't use the factory.
  2. More code.

I thought also about passing validator to named constructor

class Password
{
   private string $password;

   private function __construct(string $password)
   {
      $this->password = $password;
   }

   public static function createValidated(string $password, PasswordValidatorInterface $validator): self
   {
      $validator->validate($password);

      return new self($password);
   }
}
Racoon77x
  • 15
  • 2

1 Answers1

0

A value object should always be instantiated in a valid state. You should validate internally in the constructor whatever that validation is.

Now, what constitutes validity is entirely up to your domain and how you intend to use the objects.

Perhaps rather than focusing on a fully-validated Password object, you create an UnvalidatedPassword object. It can internally set rules like "must be a string, at least 8 characters" and other basic validation. And this object is passed to a service for further checking.

Another option is that your Password object holds a flag for if it has been externally validated yet. Again, the object should be in a valid state, not necessarily be fully vetted by your domain logic. (Either are viable options, still!)

And still, sometimes a value object is just a nice way to wrap values and give them context. They don't always need perfect internal validation. Sending your UnvalidatedPassword to a service that ends up returning a ValidPassword object is yet another perfectly acceptable option.

The community is correct to close this as opinion-based; all of this is my opinion. But it's a cool topic.

Aken Roberts
  • 13,012
  • 3
  • 34
  • 40