2017-06-22 65 views
8

爲了重用代碼,我創造了我自己的驗證規則在一個名爲ValidatorServiceProvider文件:Laravel 5.4 - 如何使用多個錯誤消息相同的自定義的驗證規則

class ValidatorServiceProvider extends ServiceProvider 
{ 
    public function boot() 
    { 
     Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) { 
      $user = User::where('email', $value)->first(); 

      // Email has not been found 
      if (! $user) { 
       return false; 
      } 

      // Email has not been validated 
      if (! $user->valid_email) { 
       return false; 
      } 

      return true; 
     }); 
    } 

    public function register() 
    { 
     // 
    } 
} 

而且我用這個規則像這樣:

public function rules() 
{ 
    return [ 
     'email' => 'bail|required|checkEmailPresenceAndValidity' 
    ]; 
} 

但是,我要爲每一種情況下不同的錯誤信息,像這樣:

if (! $user) { 
    $WHATEVER_INST->error_message = 'email not found'; 
    return false; 
} 

if (! $user->valid_email) { 
    $WHATEVER_INST->error_message = 'invalid email'; 
    return false; 
} 

但我不知道如何做到這一點,而不做2個不同的規則......
當然,它可以處理多個規則,但它也會執行多個SQL查詢,我真的想避免這種情況。
另外,請記住,在實際情況下,我可以在單個規則中擁有超過2個類似於論文的驗證。

有沒有人有想法?

=====
編輯1:

其實,我覺得我想要的東西,在比beetweensize規則同樣的方式工作。
它們代表一個單一的規則,但提供多個錯誤消息:

'size'     => [ 
    'numeric' => 'The :attribute must be :size.', 
    'file' => 'The :attribute must be :size kilobytes.', 
    'string' => 'The :attribute must be :size characters.', 
    'array' => 'The :attribute must contain :size items.', 
], 

Laravel檢查該值代表數字,一個文件,一個字符串或陣列;並獲取正確的錯誤消息以供使用。
我們如何用自定義規則實現這種事情?

回答

3

不幸的是,Laravel目前沒有提供具體的方法來直接從屬性參數數組中添加和調用驗證規則。但這並不排除基於TraitRequest使用情況的潛在友好解決方案。

請在下面找到我的解決方案。

首先是等待表單處理以抽象類處理表單請求ourelve。你需要做的是獲得當前的Validator實例,並在出現任何相關錯誤時阻止它進一步驗證。否則,你將存儲驗證實例,並調用您的自定義用戶驗證規則功能,您將在稍後創建:

<?php 

namespace App\Custom\Validation; 

use \Illuminate\Foundation\Http\FormRequest; 

abstract class MyCustomFormRequest extends FormRequest 
{ 
    /** @var \Illuminate\Support\Facades\Validator */ 
    protected $v = null; 

    protected function getValidatorInstance() 
    { 
     return parent::getValidatorInstance()->after(function ($validator) { 
      if ($validator->errors()->all()) { 
       // Stop doing further validations 
       return; 
      } 
      $this->v = $validator; 
      $this->next(); 
     }); 
    } 

    /** 
    * Add custom post-validation rules 
    */ 
    protected function next() 
    { 

    } 
} 

下一步是創建Trait將提供驗證您輸入的感謝方式當前的校驗器實例和處理您要正確的錯誤消息顯示:

<?php 

namespace App\Custom\Validation; 

trait CustomUserValidations 
{ 
    protected function validateUserEmailValidity($emailField) 
    { 
     $email = $this->input($emailField); 

     $user = \App\User::where('email', $email)->first(); 

     if (! $user) { 
      return $this->v->errors()->add($emailField, 'Email not found'); 
     } 
     if (! $user->valid_email) { 
      return $this->v->errors()->add($emailField, 'Email not valid'); 
     } 

     // MORE VALIDATION POSSIBLE HERE 
     // YOU CAN ADD AS MORE AS YOU WANT 
     // ... 
    } 
} 

最後,不要忘了你的擴展MyCustomFormRequest。例如,你的php artisan make:request CreateUserRequest後,這裏的簡單的方法來做到這一點:

<?php 

namespace App\Http\Requests; 

use App\Custom\Validation\MyCustomFormRequest; 
use App\Custom\Validation\CustomUserValidations; 

class CreateUserRequest extends MyCustomFormRequest 
{ 
    use CustomUserValidations; 

    /** 
    * Add custom post-validation rules 
    */ 
    public function next() 
    { 
     $this->validateUserEmailValidity('email'); 
    } 

    /** 
    * Determine if the user is authorized to make this request. 
    * 
    * @return bool 
    */ 
    public function authorize() 
    { 
     return true; 
    } 

    /** 
    * Get the validation rules that apply to the request. 
    * 
    * @return array 
    */ 
    public function rules() 
    { 
     return [ 
      'email' => 'bail|required|email|max:255|unique:users', 
      'password' => 'bail|required', 
      'name' => 'bail|required|max:255', 
      'first_name' => 'bail|required|max:255', 
     ]; 
    } 
} 

我希望你在我的建議找到自己的方式。

+0

有趣。我認爲這個答案只是支持我在我的總體總結:做這件相當簡單的事情(根據輸入定製錯誤信息)在laravel中幾乎是不可能的!你的答案和我的都不是特別簡單。 –

+0

作爲一個簡要說明,您的特質依賴於它在用於工作的類中的實現細節的行爲,這通常會導致非常脆弱的應用程序。詳細地說,你的特質依賴於'$ this-> v'中的驗證器,但它只是在那裏,因爲'MyCustomFormRequest'填充它。這意味着這個特質根本不可重用,這就破壞了特質的目的。我認爲你最好放棄這個特性並將'validateUserEmailAndValidity'移動到'CreateUserRequest'中,或者讓驗證器成爲你的特性中的一個必需參數,而不是從'$ this-> v'中獲取。 –

0

你在哪裏找到了大小驗證的錯誤消息?

我查找了 Illuminate\Validation\ConcernsValidatesAttributes特徵中的驗證規則,並且所有函數都返回一個布爾值(也是大小驗證)。

protected function validateSize($attribute, $value, $parameters) 
{ 
    $this->requireParameterCount(1, $parameters, 'size'); 

    return $this->getSize($attribute, $value) == $parameters[0]; 
} 

什麼你已經找到屬於這一部分:

$keys = ["{$attribute}.{$lowerRule}", $lowerRule]; 

在這種情況下,它僅適用於通過設置lowerRule值格式化輸出,即laravel在特殊情況處理,如尺寸驗證:

// If the rule being validated is a "size" rule, we will need to gather the 
    // specific error message for the type of attribute being validated such 
    // as a number, file or string which all have different message types. 
    elseif (in_array($rule, $this->sizeRules)) { 
     return $this->getSizeMessage($attribute, $rule); 
    } 

所以只要驗證規則必須返回一個布爾值,就沒有辦法返回多個錯誤消息。否則,你必須重寫驗證規則的一些方。

你的問題,你可以使用驗證的方法的存在驗證:

public function rules() 
{ 
    return [ 
     'email' => ['bail', 'required', Rule::exists('users')->where(function($query) { 
     return $query->where('valid_email', 1); 
    })] 
    ]; 
} 

所以你需要存在驗證規則。我建議使用laravel中現有的一個來檢查電子郵件是否已設置,並使用自定義的來檢查帳戶是否已驗證。

5

自定義驗證規則的處理不當是爲什麼我放棄了拉拉維爾(嗯,這是很多原因之一,但它是打破駱駝背的稻草,可以這麼說)。但無論如何,我有三個部分的答案給你:你不想在這個特定情況下這樣做的原因,快速概述你必須處理的混亂,然後在你的問題的答案你仍然想要這樣做。

重要的安全問題管理登錄

最佳安全實踐表明你應該總是返回一個一般的錯誤信息進行登錄的問題。典型的反例是,如果您返回「該電子郵件未在我們的系統中註冊」,而未找到電子郵件,並且錯誤密碼爲正確的電子郵件,則返回「錯誤密碼」。如果您提供單獨的驗證消息,則可以向潛在攻擊者提供有關如何更有效地指揮其攻擊的更多信息。因此,所有與登錄相關的問題都應該返回一個通用的驗證消息,而不管其根本原因如何,以及「無效的電子郵件/密碼組合」的效果。對於密碼恢復表單也是如此,這些表單通常會說:「密碼恢復說​​明已發送到該電子郵件,如果它存在於我們的系統中」。否則,你會給攻擊者(和其他人)一種方法來知道你的系統註冊了哪些電子郵件地址,並且可以暴露額外的攻擊媒介。所以在這個特定的情況下,一個驗證信息就是你想要的。

的麻煩laravel

你碰到的問題是,laravel驗證簡單地返回true或false表示該規則是否被滿足。錯誤消息分開處理。您無法從驗證器邏輯中指定驗證器錯誤消息。我知道。這很荒謬,而且計劃不周。你所能做的只是返回真或假。您無法訪問其他任何內容來幫助您,所以您的僞代碼不會執行此操作。

的(醜陋的)答案

創建自己的驗證消息最簡單的方法就是create your own validator。這看起來是這樣的(你的控制器內):

$validator = Validator::make($input, $rules, $messages); 

你仍然要創建開機驗證(您Valiator::Extend電話然後就可以正常通過使它們到您的自定義驗證指定$rules最後,你可以指定你的郵件這樣的事情,總(控制器內):

public function login(Request $request) 
{ 
    $rules = [ 
     'email' => 'bail|required|checkEmailPresenceAndValidity' 
    ] 

    $messages = [ 
     'checkEmailPresenceAndValidity' => 'Invalid email.', 
    ]; 

    $validator = Validator::make($request->all(), $rules, $messages); 
} 

(我不記得,如果你要指定您$messages陣列中的每個規則,我不這麼認爲)當然,即使這不是很棒,因爲你爲$ message傳遞的只是一個數組字符串(這就是允許的)。因此,您仍然不能根據用戶輸入輕易更改此錯誤消息。這一切都發生在驗證器運行之前。您的目標是根據驗證結果讓驗證消息發生變化,但laravel會強制您首先生成消息。因此,要真正做你想做的事情,你必須調整系統的實際流程,這並不是很棒。

解決方案是在您的控制器中有一個方法來計算您的自定義驗證規則是否被滿足。在你做驗證器之前,它會這樣做,以便你可以發送一個合適的消息給你構建的驗證器。然後,當您創建驗證規則時,只要將您的規則定義移動到控制器中,您也可以將其與綁定到驗證計算器的結果綁定。你只需要確定,而不是意外地把事情搞亂。您還必須記住,這需要將您的驗證邏輯移到驗證器之外,這是相當黑客。不幸的是,我95%確定沒有其他辦法可以做到這一點。

我在下面有一些示例代碼。它肯定有一些缺點:你的規則不再是全局的(它在控制器中定義),驗證邏輯移出驗證器(違反了最小驚訝原則),你將不得不提出一個in - 對象緩存方案(這不難),以確保您不會執行兩次查詢,因爲驗證邏輯被調用兩次。重申一下,這絕對是不吉利的,但我相當肯定這是用laravel做你想做的唯一方法。可能有更好的方法來組織這個,但這至少應該讓你知道你需要做什麼。

<?php 
namespace App\Http\Controllers; 

use User; 
use Validator; 
use Illuminate\Http\Request; 
use App\Http\Controllers\Controller; 

class LoginController extends Controller 
{ 
    public function __construct() { 
     Validator::extend('checkEmailPresenceAndValidity', function ($attribute, $value, $parameters, $validator) { 

      return $this->checkLogin($value) === true ? true : false; 

     }); 
    } 

    public function checkLogin($email) { 
     $user = User::where('email', $email)->first(); 

     // Email has not been found 
     if (! $user) { 
      return 'not found'; 
     } 

     // Email has not been validated 
     if (! $user->valid_email) { 
      return 'invalid'; 
     } 

     return true; 
    } 

    public function login(Request $request) { 

     $rules = [ 
      'email' => 'bail|required|checkEmailPresenceAndValidity' 
     ] 

     $hasError = $this->checkLogin($request->email); 
     if ($hasError === 'not found') 
      $message = "That email wasn't found"; 
     elseif ($hasError === 'invalid') 
      $message = "That is an invalid email"; 
     else 
      $message = "Something was wrong with your request"; 


     $messages = [ 
      'checkEmailPresenceAndValidity' => $message, 
     ]; 

     $validator = Validator::make($request->all(), $rules, $messages); 

     if ($validator->fails()) { 
      // do something and redirect/exit 
     } 

     // process successful form here 
    } 
} 

此外,值得一快速的注意,此實現依賴於閉包,這(我相信)$this支持PHP 5.4中添加。如果您使用的是舊版本的PHP,則必須提供$this來關閉use

編輯咆哮

它真正歸結爲是,laravel驗證系統的設計是非常精細的。每個驗證規則都是專門用來驗證一件事情的。因此,給定驗證器的驗證消息不應該被更改,因此$messages(當您構建自己的驗證器時)只接受普通字符串。

一般來說,粒度在應用程序設計中是一件好事,而SOLID原則的正確實施則力求實現。但是,這個特殊的實現讓我發瘋。我的一般編程哲學是,一個好的實現應該使最常見的用例非常簡單,然後爲不太常見的用例開闢道路。在這種情況下,laravel的體系結構使得最常見的用例變得簡單,但不太常見的用例幾乎是不可能的。我對這種交易不滿意。我對Laravel的總體印象是,只要你需要以迂迴的方式來做事,它就會很好,但如果你出於任何原因不得不跳出這個框,它會積極地讓你的生活變得更加困難。在你的情況下,最好的答案是可能只是立即回到那個框中,即,即使它意味着進行冗餘查詢也要創建兩個驗證器。對應用程序性能的實際影響可能根本無關緊要,但是您將獲得長期可維護性,以使您的應用程序表現得相當大。

+0

有趣的閱讀。謝謝。 – WhyAyala