Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/242.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 如何在ValueObject中使用可重用验证_Php_Validation_Dependency Injection_Domain Driven Design_Value Objects - Fatal编程技术网

Php 如何在ValueObject中使用可重用验证

Php 如何在ValueObject中使用可重用验证,php,validation,dependency-injection,domain-driven-design,value-objects,Php,Validation,Dependency Injection,Domain Driven Design,Value Objects,我正试着结合一些技巧来提高我的头脑 最好不要创建无效的ValueObject。只要提供的内容不足以创建有效的ValueObject,ValueObject构造函数就会失败。在我的示例中,只有存在值时才能创建EmailAddress对象。到目前为止,一切顺利 验证提供的电子邮件地址的价值,这就是我开始怀疑这些原则的地方。我有四个例子,但我不知道哪一个应该被视为最佳实践 示例1很简单:只需一个构造函数、一个必需的参数“value”和一个单独的函数validate,以保持代码干净。所有的验证代码都留在

我正试着结合一些技巧来提高我的头脑

最好不要创建无效的ValueObject。只要提供的内容不足以创建有效的ValueObject,ValueObject构造函数就会失败。在我的示例中,只有存在值时才能创建EmailAddress对象。到目前为止,一切顺利

验证提供的电子邮件地址的价值,这就是我开始怀疑这些原则的地方。我有四个例子,但我不知道哪一个应该被视为最佳实践

示例1很简单:只需一个构造函数、一个必需的参数“value”和一个单独的函数validate,以保持代码干净。所有的验证代码都留在类内部,外部世界永远无法使用。该类只有一个用途:存储emailaddress,并确保它永远不会是无效的。但是代码永远不会被重用——我用它创建了一个对象,仅此而已

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
}
示例2使验证函数成为静态函数。该函数永远不会更改类的状态,因此它正确地使用了static关键字,并且其中的代码永远无法将任何内容更改为从嵌入静态函数的类创建的任何实例。但是如果我想重用代码,我可以调用静态函数。不过,我觉得这很肮脏

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
}
示例3引入了另一个类,在我的对象主体内硬编码。另一个类是一个验证类,包含验证代码,因此创建了一个可以在需要验证类的任何时候和任何地方使用的类。该类本身是硬编码的,这也意味着我在该验证类上创建了一个依赖项,它应该总是在附近,而不是通过依赖项注入来注入。可以说,硬编码验证器与将完整代码嵌入到对象中一样糟糕,但另一方面:DI很重要,通过这种方式,必须创建一个新类(扩展或简单地重写)来简单地更改依赖关系

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);
}
示例4再次使用validator类,但将其放入构造函数中。因此,在创建类之前,我的ValueObject需要已经存在并创建了一个验证器类,但可以轻松覆盖验证器。但是对于一个简单的ValueObject类来说,在构造函数中有这样一个依赖关系有多好呢?因为唯一真正重要的是值,我不应该关心如何以及在哪里处理电子邮件是否正确,并提供一个正确的验证器

public function __construct ($value, \Validator $validator)
{
    if ( $validator->validate($value) )
    {
        throw new \ValidationException('This is not an emailaddress.');
    }
    $this->value = $value;
}
我开始考虑的最后一个例子是,提供一个默认的验证器,同时可以通过DI在构造函数中为验证器注入覆盖。但当覆盖最重要的部分:验证时,我开始怀疑简单的ValueObject有多好

所以,任何人都有一个答案,就是最好用哪种方式来编写这个类,这对于像电子邮件地址这样简单的东西,或者更复杂的东西,比如条形码或visa卡,或者任何你可能想到的东西都是正确的,并且不违反DDD、DI、OOP、DRY、错误使用静态等等

完整代码:

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;
}

}
例4

为什么??因为它是可测试的,简单明了

根据验证器的实际操作(在某些情况下,验证器可能依赖于API调用或对数据库的调用),可注入验证器完全可以通过模拟进行测试。在我刚才提到的情况下,所有其他的测试要么是不可能测试的,要么是难以置信的难以测试

<强>编辑:< /强>对于那些想知道依赖注入方法如何帮助测试的人,请考虑下面的CalpRealValueCube类,它使用一个标准的AkISMET垃圾邮件检查库。

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;
            }
        }
    }
}
下面,当你在设置你的单元测试时,我们有一个CommentValidatorMock类,我们用它来代替,我们有setter来手动改变我们可以拥有的2个输出bool,我们有上面模拟的2个函数,可以输出我们想要的任何东西,而不需要通过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;
    }
}

如果你认真对待单元测试,那么你需要使用DI。

第一直觉通常是最好的。您应该使用第一个选项。EmailAddress是一个值对象。它可以在其他值对象或实体中重用。我不明白你为什么认为它不能重复使用。您可以在其他有界上下文中使用这些公共值对象的“共享库”。小心你放进去的东西。如果这在概念上是可能的,那么它们需要真正的通用性。

我认为,如果使用单独的验证方法或将验证程序移动到单独的类中,将是黄油和干黄油


类电子邮件地址{
受保护的美元价值;
公共函数构造($value)
{
$this->value=\validateEmailAddress($value);
}
}
函数validateEmailaddress(字符串$value):字符串
{
如果(!是字符串($value)){
抛出new\ValidationException('这不是电子邮件地址');
}//错误的函数,只是一个示例
返回$value;
}
//或者对于严格的OOP人员
最终类验证程序{
私有函数_构造(){}
公共静态函数validateEmailaddress(字符串$input):字符串{…}
}
//我甚至更喜欢使用from(FP monads)
接口值OBEjCarror{}
类InvalidMail实现ValueObjectError{}
函数validateEmailaddress(字符串$input):要么{
//如果php支持泛型会更好,所以使用其中任何一种都更具可读性,但不幸的是,php并没有泛型类型,也许将来会这样
返回为字符串($input)
?新权利($input)
:新建左侧(新建InvalidEmail());
}

在示例4中使用默认验证器会有什么问题吗,只要我没有向constru提交验证器