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

【Laravel】入力エラーのとき「ファイルをあげ直してね」メッセージを出すよう対応したよ

まえがき

最近はLaravelを触ってます。

で、バリデーションまわりを触っていたのだけど、ファイルアップロードで困ったことが。
エラーがあって戻されたときに画像ファイルの情報が消し飛んでしまって何も分からない。。
「ファイル残してほしい」とは言わないけど、
せめてどこのファイルをあげようとしていたのか教えてほしいなぁ、と思ったので対応したよ。

前提

  • PHP7.3.6
    • Laravel 5.5.45

なぜ消えるのか

バリデーションエラーが発生した場合、自動で前ページに戻される。
その際に入力時のデータもセッション内に保存してくれているのだけど、この部分で意図的に消しているからだった。

ExceptionHandler

正確には Illuminate\Foundation\Exceptions\Handler
ValidationException発生時、なにもしていなければこのクラスの render に入ってきて、
convertValidationExceptionToResponse からの invalid へ渡す。

この invalid で前ページに戻る処理をしているのだけど、
その際に渡す入力元データをリクエストを元に生成している。

redirect

正確には Illuminate\Http\RedirectResponse
この中の withInput からの removeFilesFromInput

Illuminate\Http\RedirectResponse
    protected function removeFilesFromInput(array $input)
    {
        foreach ($input as $key => $value) {
            if (is_array($value)) {
                $input[$key] = $this->removeFilesFromInput($value);
            }

            if ($value instanceof SymfonyUploadedFile) {
                unset($input[$key]);
            }
        }

        return $input;
    }

SymfonyUploadedFile のインスタンスをバッサリ削除してた。
そりゃ何も取れない訳だ・・・・('A`)

SymfonyUploadedFileを消させない

こんな根っこから対応が入ってるんじゃ手を入れづらい・・・・と思ったけど、そんなことはなく。

ExceptionHandler::convertValidationExceptionToResponse を見直す。

Illuminate\Foundation\Exceptions\Handler
    protected function convertValidationExceptionToResponse(ValidationException $e, $request)
    {
        if ($e->response) {
            return $e->response;
        }

        return $request->expectsJson()
                    ? $this->invalidJson($request, $e)
                    : $this->invalid($request, $e);
    }

引数である ValidationException $e にレスポンスが入っていれば、それ以降の処理はせず、直接使う。
てことは、ここより前に $e->response をつけてやればいい。

\App\Exceptions\Handler
    protected function convertValidationExceptionToResponse($e, $request)
    {
        $url = $exception->redirectTo ?? url()->previous();

        $inputs = $request->all();
        foreach ($inputs as $key => $value) {
            if (is_array($value)) {
                $inputs[$key] = $this->removeFilesFromInput($value);
            }

            if ($value instanceof SymfonyUploadedFile) {
                // 元ファイル名だけ返す
                $inputs[$key] = basename($value->getClientOriginalName());
            }
        }

        $e->response = redirect($url)
            ->withInput($inputs)
            ->withErrors(
                $e->errors(),
                $e->errorBag
            );

        return parent::convertValidationExceptionToResponse($e, $request);
    }

ファイル名くらいはあってもいいかな、と思って入れたけど、
別に「あげ直してね」ていうくらいならTRUE/FALSEでもいいね。。

テンプレート
    <table>
      <th>画像ファイル</th>
      <td>
        {{ Form::file('image') }}
        @if ($errors->has('image'))
          <p>{{ $errors->first('image') }}</p>
        @elseif ($errors->any())
          <p>ファイルをあげ直してください: {{ old('image') }}</p>
        @endif
      </td>
    </table>

テンプレートが雑なのはご愛嬌。。
10個くらいフォーム作って出した方が分かりやすいかとは思ったんだけど、メンドかった(´・ω・`)

あとがき

ちょっと調べるのに時間がかかったのでメモ的に。
とはいえ、Laravel触り始めだから、基底クラスをめぐるちょうどいい機会ではあったかな(^-^)

まぁ、需要があるかは分からないけど。。。

Why do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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