何が起こったか
例えば maxlength=10のtextareaがあったとして、このような入力をする。
1[改行]
2[改行]
3[改行]
4[改行]
5[改行]
form.html
<html>
(省略)
<textarea name="body_text" maxlength="10"></textarea>
(省略)
</html>
FormRequestを継承したRequestFileでこういうRuleを指定したとすると、弾かれてしまう。
XxxRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class InformationRegistrationRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
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
namespace Illuminate\Validation\Concerns;
trait ValidatesAttributes
{
(省略)
/**
* Validate the size of an attribute is less than a maximum value.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateMax($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'max');
if ($value instanceof UploadedFile && ! $value->isValid()) {
return false;
}
return $this->getSize($attribute, $value) <= $parameters[0];
}
(省略)
/**
* Get the size of an attribute.
*
* @param string $attribute
* @param mixed $value
* @return mixed
*/
protected function getSize($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)) {
return count($value);
} elseif ($value instanceof File) {
return $value->getSize() / 1024;
}
return mb_strlen($value); // <----------------------- ここ
}
(省略)
}
やったこと
てっとりばやく、かつ見逃しにくい方法として、バリデーション実行前に改行コードを上書きすることにした。
(ミドルウェアで変換するなども検討したものの、変換処理を見逃しそうだなと考えて保留、もっといい方法がある気がする。)
FormRequestのvalidationData
をOverRideし、\r\n
を\n
へ変換する。
XxxRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class InformationRegistrationRequest extends FormRequest
{
/**
* @overRide
* Get data to be validated from the request.
*
* @return array
*/
protected function validationData()
{
$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
*/
public function rules()
{
return [
'body_text' => ['max:10'],
];
}
}
注意点
Controllerで入力値を受け取る時は、バリデートされた値を取得するvalidated()
を利用しないと、変換後のデータを取ってこれない。
XxxController.ophp
<?php
namespace App\Http\Controllers;
use App\Http\Requests\XxxRequest;
class XxxController extends Controller
{
public function register(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
}
}