何が起こったか
例えば maxlength=10のtextareaがあったとして、このような入力をする。
1[改行]
2[改行]
3[改行]
4[改行]
5[改行]
form.html
<html>
(省略)
<textareaname="body_text"maxlength="10"></textarea>
(省略)
</html>
FormRequestを継承したRequestFileでこういうRuleを指定したとすると、弾かれてしまう。
XxxRequest.php
<?phpnamespaceApp\Http\Requests;useIlluminate\Foundation\Http\FormRequest;classInformationRegistrationRequestextendsFormRequest{/**
* Get the validation rules that apply to the request.
*
* @return array
*/publicfunctionrules(){return['body_text'=>['max:10'],];}}
なぜ弾かれるか
原因は、この問題が再現した環境の改行コード差異。
役割 | OS | 改行コード |
---|---|---|
Client側 | Windows | \r\n |
Server側 | Linux | \n |
Client側では、\r\n
は1文字として扱われるが、Server側では、maxルールで使われているmb_strlen()
により2文字にカウントされてしまう。vendor\laravel\framework\src\Illuminate\Validation\Concerns\ValidatesAttributes.php
ValidatesAttributes.php
namespaceIlluminate\Validation\Concerns;traitValidatesAttributes{(省略)/**
* Validate the size of an attribute is less than a maximum value.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/publicfunctionvalidateMax($attribute,$value,$parameters){$this->requireParameterCount(1,$parameters,'max');if($valueinstanceofUploadedFile&&!$value->isValid()){returnfalse;}return$this->getSize($attribute,$value)<=$parameters[0];}(省略)/**
* Get the size of an attribute.
*
* @param string $attribute
* @param mixed $value
* @return mixed
*/protectedfunctiongetSize($attribute,$value){$hasNumeric=$this->hasRule($attribute,$this->numericRules);// This method will determine if the attribute is a number, string, or file and// return the proper size accordingly. If it is a number, then number itself// is the size. If it is a file, we take kilobytes, and for a string the// entire length of the string will be considered the attribute size.if(is_numeric($value)&&$hasNumeric){return$value;}elseif(is_array($value)){returncount($value);}elseif($valueinstanceofFile){return$value->getSize()/1024;}returnmb_strlen($value);// <----------------------- ここ}(省略)}
やったこと
てっとりばやく、かつ見逃しにくい方法として、バリデーション実行前に改行コードを上書きすることにした。
(ミドルウェアで変換するなども検討したものの、変換処理を見逃しそうだなと考えて保留、もっといい方法がある気がする。)
FormRequestのvalidationData
をOverRideし、\r\n
を\n
へ変換する。
XxxRequest.php
<?phpnamespaceApp\Http\Requests;useIlluminate\Foundation\Http\FormRequest;classInformationRegistrationRequestextendsFormRequest{/**
* @overRide
* Get data to be validated from the request.
*
* @return array
*/protectedfunctionvalidationData(){$all=$this->all();if(isset($all['body_text'])){$all['body_text']=preg_replace("/\r\n/","\n",$all['body_text']);}return$all;}/**
* Get the validation rules that apply to the request.
*
* @return array
*/publicfunctionrules(){return['body_text'=>['max:10'],];}}
注意点
Controllerで入力値を受け取る時は、バリデートされた値を取得するvalidated()
を利用しないと、変換後のデータを取ってこれない。
XxxController.ophp
<?phpnamespaceApp\Http\Controllers;useApp\Http\Requests\XxxRequest;classXxxControllerextendsController{publicfunctionregister(XxxRequest$request){$input=$request->validated();// "1\n1\n2\n1\n3\n4\n5" ←OK$input=$request->body_text;// "1\r\n1\r\n2\r\n1\r\n3\r\n4\r\n5" ←NG$input=$request->input('body_text');// "1\r\n1\r\n2\r\n1\r\n3\r\n4\r\n5" ←NG}}