PHP 中链接检查的优雅方式

假设您有一个通用验证函数,并且必须对输入进行多次子检查。每个子检查也返回布尔值。请看这个虚构的:


private function hasValidContent(string $content) : bool

{

    $isValid = false;

    $isValid = $this->hasMoreThanOneChar($content);

    $isValid = $this->hasValidCharacters($content);

    $isValid = $this->hasCapitalLetters($content);

    ...

    

    return $isValid;


}

当然,上面的代码不会工作,因为接下来的每一次检查都会覆盖前一次的评估。


但是,当第一次检查导致 时,如何才能停止进一步的检查呢false?例如,如果其内容不超过一个字符,那么它应该在函数之后停止hasMoreThanOneChar并立即将方法返回hasValidContent为 false。


是的,当然,您可以在每次方法调用后检查表达式是否变为 false,但这很尴尬,而且开销很大且重复。


||如果有数千个或&&类似的东西在一个单一的中做这件事感觉真的很难看


return $this->checkA($content) 

   && $this->checkB($content)

   && $this->checkC($content)

  ...;

表达式多了之后,可读性就会受到影响。


另一种经常提到的方法可能是使用异常


private function hasValidContent(string $content) : bool

{

    try {

        $this->hasMoreThanOneChar($content);

        $this->hasValidCharacters($content);

        $this->hasCapitalLetters($content);

        ...

    }

    catch {

        return false;

    }

    return true;


}


private function hasMoreThanOneChar(string $string) : void {

    if(count($string) < 2 ) {

      throw new HasNotMoreThanOneCharException(...)

    }

}

但我也认为这不是一个优雅的解决方案,因为验证内容并不例外。


所以我的问题是:


有什么优雅的模式吗?有什么关键词可以搜索吗?


慕森王
浏览 122回答 3
3回答

蝴蝶不菲

创建一个包含您要执行的所有验证的数组。数组的每个元素都必须是可调用的。迭代并尽早退出。如果所有验证器都通过了,那就好了。private function hasValidContent(string $content) : bool{    $validators = [        // instance method        [$this, 'hasMoreThanOneChar'],        // callable class        new class {            public function __invoke(string $content)            {                return ...;            }        },        // anonymous function        function (string $content): bool {            return ...;        }    ];    foreach ($validators as $validate) {        if (!$validate($content)) {            return false;        }    }    return true;}

幕布斯6054654

我推断您正在寻找责任链模式。例如,假设您有三个类:锁、警报和灯,用于检查房屋的状态。abstract class HomeChecker {&nbsp; &nbsp; protected $successor;&nbsp; &nbsp; public abstract function check(Home $home);&nbsp; &nbsp; public function succeedWith($successor) {&nbsp; &nbsp; &nbsp; &nbsp; $this->successor = $successor;&nbsp; &nbsp; }&nbsp;&nbsp; &nbsp; public function next(Home $home) {&nbsp; &nbsp; &nbsp; &nbsp; $this->successor->check($home);&nbsp; &nbsp; }}class HomeStatus {&nbsp; &nbsp; &nbsp;public $locked = true;&nbsp; &nbsp; &nbsp;public $alarmOn = true;&nbsp; &nbsp; &nbsp;public $lightsOff = true;}class Locks extends HomeChecker {&nbsp; &nbsp;public function check(Home $home) {&nbsp; &nbsp; &nbsp; &nbsp;if(!$home->locked) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;throw new Exception('The doors are not locked'); // or basically whatever you want to do&nbsp; &nbsp; &nbsp; &nbsp;}&nbsp; &nbsp; &nbsp; &nbsp;$this->next($home);&nbsp; &nbsp;}}class Alarm extends HomeChecker {&nbsp; &nbsp;public function check(Home $home) {&nbsp; &nbsp; &nbsp; &nbsp;if(!$home->alarmOn) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;throw new Exception('The Alarms are not on'); // or basically whatever you want to do&nbsp; &nbsp; &nbsp; &nbsp;}&nbsp; &nbsp; &nbsp; &nbsp;$this->next($home);&nbsp; &nbsp;}}class Lights extends HomeChecker {&nbsp; &nbsp;public function check(Home $home) {&nbsp; &nbsp; &nbsp; &nbsp;if(!$home->lightsOff) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;throw new Exception('The lights are still on!'); // or basically whatever you want to do&nbsp; &nbsp; &nbsp; &nbsp;}&nbsp; &nbsp; &nbsp; &nbsp;$this->next($home);&nbsp; &nbsp;}}$locks = new Locks();$alarm = new Alarm();$lights = new Lights();$locks->succeedWith(new Alarm); // set the order in which you want the chain to work$alarms->succeedWith(new Lights);$locks->check(new HomeStatus);所有三个类:Locks、Lights 和 Alarm 都扩展了一个名为HomeChecker的类,该类基本上具有一个抽象检查方法以确保子类确实实现该方法以及两个名为successWith和next 的方法。successWith 方法用于设置一个后继者,如果当前执行的方法返回 true(意味着没有错误),则将调用该后继者。下一个方法本质上调用了链接起来的下一个方法。通过这一行,我们开始了这个链,如果locks类的check 方法$locks->check(new HomeStatus);中的检查失败,则会抛出一个错误,并且执行将停止,否则它将调用HomeChecker 类的下一个方法,该方法最终将调用 check 方法我们安排的下一堂课$locks->succeedWith(new Alarm); // set the order in which you want the chain to work$alarms->succeedWith(new Lights);在这些行中。所以通过这种方式我们可以非常优雅地进行链式检查。我希望这能帮到您。

动漫人物

一个建议是让验证方法抛出 anException而不是返回trueand false(成功时不返回任何内容)。例如:Class Validator {&nbsp; &nbsp; public static function assertString($string) {&nbsp; &nbsp; &nbsp; &nbsp; if (!is_string($string)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new Exception('Type mismatch.');&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; public static function assertStringLength($string, $min, $max) {&nbsp; &nbsp; &nbsp; &nbsp; if (strlen($string) < $min || strlen($string) > $max) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new Exception('String outside of length boundaries.');&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}你的代码看起来像这样:try {&nbsp; &nbsp; Validator::assertString($string);&nbsp; &nbsp; Validator::assertStringLength($string, 4, 50);} catch (Exception $e) {&nbsp; &nbsp; // handle invalid input}请注意,我的函数是静态的,在这种情况下有意义,但不一定总是如此。
打开App,查看更多内容
随时随地看视频慕课网APP