61

php – 如何在ValueObject中使用可重用验证

 5 years ago
source link: https://codeday.me/bug/20190112/504635.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我正试图结合一些技巧.

从来没有可能创建一个无效的ValueObject似乎是一种好习惯.只要提供的内容不足以创建有效的ValueObject,ValueObject构造函数就会失败.在我的示例中,只有存在值时才能创建EmailAddress对象.到现在为止还挺好.

验证提供的电子邮件地址的价值,这就是我开始怀疑原则的地方.我有四个例子,但我不知道哪一个应该被认为是最好的做法.

示例1很简单:只需构造函数,必需参数“value”和单独的函数验证以保持代码清洁.所有验证代码都保留在类中,并且永远不会对外界可用.该类只有一个目的:存储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使validate函数成为静态函数.该函数永远不会改变类的状态,因此它正确使用了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再次使用验证器类,但将其放在构造函数中.因此,在创建类之前,我的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有多好:验证.

所以,任何人都有一个答案,哪种方式最适合写这个课程,这对于像电子邮件地址这样简单的东西,或者像条形码或签证卡或任何人可能想到的更复杂的东西都是正确的,并且不违反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调用或对数据库的调用),可注入验证器可通过模拟完全测试.所有其他的都要么在我刚刚提到的情况下无法测试,要么难以测试.

编辑:对于那些想知道依赖注入方法如何帮助测试的人,请考虑下面使用标准Akismet垃圾邮件检查库的CommentValidator类.

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类,我们有设置器来手动更改我们可以拥有的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.

翻译自:https://stackoverflow.com/questions/21268974/how-to-use-reusable-validation-in-a-valueobject


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK