簡體   English   中英

PHP 中鏈接檢查的優雅方式

[英]Elegant way of chaining checks in PHP

假設您有一個常規驗證 function 並且您必須對輸入進行幾次子檢查。每個子檢查還返回 boolean。 看這個虛構的:

private function hasValidContent(string $content) : bool
{
    $isValid = false;
    $isValid = $this->hasMoreThanOneChar($content);
    $isValid = $this->hasValidCharacters($content);
    $isValid = $this->hasCapitalLetters($content);
    ...
    
    return $isValid;

}

當然,上面的代碼不會起作用,因為每次下一次檢查都會覆蓋前一次的評估。

但是,當第一次檢查導致false時,如何停止進一步檢查? 例如,如果它的內容不超過一個字符,那么它應該在hasMoreThanOneChar function 之后停止並立即將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(...)
    }
}

但我也認為這不是一個優雅的解決方案,因為驗證東西並沒有什么特別之處。

所以我的問題:

有什么優雅的模式嗎? 我可以搜索任何關鍵字嗎?

創建一個包含您要執行的所有驗證的數組。 數組的每個元素都必須是可調用的。 迭代並提前退出。 如果所有驗證者都通過了,那就很好。

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

一個建議可以是讓驗證方法拋出Exception而不是返回truefalse (成功時不返回任何內容)。

例如:

Class Validator {
    public static function assertString($string) {
        if (!is_string($string)) {
            throw new Exception('Type mismatch.');
        }
    }
    
    public static function assertStringLength($string, $min, $max) {
        if (strlen($string) < $min || strlen($string) > $max) {
            throw new Exception('String outside of length boundaries.');
        }
    }
}

您的代碼將如下所示:

try {
    Validator::assertString($string);
    Validator::assertStringLength($string, 4, 50);
} catch (Exception $e) {
    // handle invalid input
}

請注意,我的函數是 static,在這種情況下有意義,但不一定總是如此。

我推斷您正在尋找責任鏈模式。

例如,假設您有三個類:Locks、Alarm 和 Lights,它們檢查家庭的狀態。

abstract class HomeChecker {
    protected $successor;

    public abstract function check(Home $home);

    public function succeedWith($successor) {
        $this->successor = $successor;
    } 

    public function next(Home $home) {
        $this->successor->check($home);
    }
}

class HomeStatus {
     public $locked = true;
     public $alarmOn = true;
     public $lightsOff = true;
}

class Locks extends HomeChecker {
   public function check(Home $home) {
       if(!$home->locked) {
           throw new Exception('The doors are not locked'); // or basically whatever you want to do
       }
       $this->next($home);
   }
}

class Alarm extends HomeChecker {
   public function check(Home $home) {
       if(!$home->alarmOn) {
           throw new Exception('The Alarms are not on'); // or basically whatever you want to do
       }
       $this->next($home);
   }
}

class Lights extends HomeChecker {
   public function check(Home $home) {
       if(!$home->lightsOff) {
           throw new Exception('The lights are still on!'); // or basically whatever you want to do
       }
       $this->next($home);
   }
}

$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的 class,它基本上具有一個抽象檢查方法,以確保子類確實實現了該方法和兩個名為successWithnext的方法。 succeedWith 方法用於設置一個后繼者,如果當前正在執行的方法返回 true(意味着沒有錯誤),則該后繼者將被調用。 下一個方法本質上是調用鏈接起來的下一個方法。

使用這一行$locks->check(new HomeStatus); ,我們開始鏈,如果class 的檢查方法中的檢查失敗,則會拋出錯誤並停止執行,否則它將調用HomeChecker class 的下一個方法,最終將調用下一個的檢查方法設定的class

$locks->succeedWith(new Alarm); // set the order in which you want the chain to work
$alarms->succeedWith(new Lights);

在這些行中。

所以通過這種方式,我們可以非常優雅地進行鏈式檢查。 我希望這能幫到您。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM