Help us understand the problem. What is going on with this article?

Laravelでtextareaのmaxlengthと、ValidationRuleのmaxが改行によって判定ずれする

More than 1 year has passed since last update.

何が起こったか

例えば 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
    }
}

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away