Laravel 6.x 【ファイルアップロード】 バリデーションのfileとisValidメソッドの違いを検証->必ず存在確認が必要!

#制作環境

Windows 10
Laravel : 6.18.35
Laravel/ui : 1.0
Laravel-mix : 5.0.1
Bootstrap : 4.0.0
MDBootstrap : 4.19.1
chart.js : 2.9.3
XAMPP
PHP : 7.4.3
Visual Studio Code

#はじめに
この記事はプログラミングをはじめたばかりの素人が、できたことをメモするのに利用しています。
内容には誤りがあるかもしれません。

#検証することにした経緯
とあるサイトで以下のような記述がありました(一部抜粋)。

```PHP
public function upload(Request $request)
    {
        $this->validate($request, [
            'file' => [
                // 必須
                'required',
                // アップロードされたファイルであること
                'file',
                // 画像ファイルであること
                'image',
                // MIMEタイプを指定
                'mimes:jpeg,png',
            ]
        ]);

        if ($request->file('file')->isValid([])) {
            $path = $request->file->store('public');

            $file_name = basename($path);
            $user_id = Auth::id();
            $new_image_data = new Image();
            $new_image_data->user_id = $user_id;
            $new_image_data->file_name = $file_name;

            $new_image_data->save();

            return redirect('/output');
        } else {
```

画像のアップロードについて検索している時に見つけたのですが、バリデーションルールのfileとisValidが同じ事をしているように思え、片方は記述が不要なのではないかと思い、検証してみることにしてみました。

#リファレンスの説明
###バリデーションルールのfileについて
![バリデーション.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/a61b7a75-431f-07bf-a83d-a2b81fdb375b.jpeg)


###isValidメソッドについて
![リファレンス.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/6db76419-f6c3-3b3f-09d0-f69d3b3c7b77.jpeg)

#検証前の自分の見解
バリデーションでフィールドがアップロードに成功したことの確認が取れたということは、(アップロードされているから)ファイルが存在している。

アップロードに成功しているので、問題なくアップロードができている。

故に、両方とも同じ内容で片方は不要なのではないか?

#そもそもの誤解
検証中に気づいたのですが、リファレンスの内容の解釈に誤解がありました。
isValidについて、リファレンスでは**「ファイルが存在しているかに付け加え、isValidメソッドで問題なくアップロードできたのかを確認できます。」**と記載があった為、てっきり**「ファイルの存在確認+問題なくアップロードできたか確認」**の両方をやっていると思ったのですが、実際は**ファイルの存在確認の記述にプラス(付け加えて)**で**isValidを記述すれば、問題なくアップロードできたかの確認もできる**という内容でした。

#検証にあたり
ビューはアップロードのフォームとエラー・成功時のメッセージがでるだけの簡単なものを作成し、コントローラーの記述を変更することで検証していきました。
検証に直接関係ないのでビューは記載しません。
画像のアップロードを想定しています。

ビューのイメージ

![image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/760252/114ede28-f318-8721-b048-728118c0bbde.jpeg)


#検証
##検証① バリデーションfileのみ記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\FileImage;

class FileUpController extends Controller
{
    public function index()
    {
        $data = FileImage::all();
        return view('file', compact('data'));
    }

    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'file',
        ]);
        return redirect('/file')->with('success', '登録完了しました。');
    }
}
```

**結果:エラーなし。「登録完了しました。」が表示される。**
####★注意点★
**fileのみの記載だと、バリデーションは全く機能しません。**
ファイルを選択し、送信をクリックした場合も結果は同じでした。
fileはあくまでも、**アップロードに成功したかどうか**だけを検証しているようで、**アップロードの操作が内部で行われていなけば、全く機能しません。**

ファイル選択なし⇒ファイルがないので、アップロードの操作なし⇒file機能せず⇒バリデーション通過
ファイル選択あり⇒ファイルがあるので、アップロードの操作あり⇒fileバリデーション実行⇒バリデーション通過

**つまり、必ずファイル自体の存在確認(require等)が必須です。**

##検証② バリデーションfileのみ記載 画像に対する処理あり
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'file',
        ]);

        $path = $request->img->store('public/images');
        $filename = basename($path);

        $data = new FileImage;
        $data->file_name = $filename;
        $data->save();

        return redirect('/file')->with('success', '登録完了しました。');
    }
```

**結果:エラー発生「Call to a member function store() on null」。**
ファイルの存在確認をしていない為、ファイルが無い(空:null)時の処理でエラーが発生する。
※そもそもこういう処理の書き方はしないと思いますが、細かな動作を確認したい為、わざとやってます。

検証①の結果の通り、fileのみの記載はほぼザルなので、**やはり必ずファイル自体の存在確認が必須です。**

##検証③ isValidメソッドのみ記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        if ($request->file('img')->isValid()) 
            return redirect('/file')->with('success', '登録完了しました。');
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:エラー発生「Call to a member function isValid() on null」。**
メソッド自体が機能せずエラーが発生。

**やはり、必ずファイル自体の存在確認が必須です。**

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証④ isValidメソッドのみ記載 画像に対する処理あり
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

**結果:エラー発生「Call to a member function isValid() on null」。**
そもそもファイルが無いと、検証③で試した通りisValidがエラーになるので、検証するまでもない。
なので、コントローラの記述は掲載しません。

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証⑤ バリデーションfileとisValidメソッド両方記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'file',
        ]);

        if ($request->file('img')->isValid()) {
            return redirect('/file')->with('success', '登録完了しました。');
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:エラー発生「Call to a member function isValid() on null」。**
fileはザルの状態なので、検証③と同じ結果になります。
結果が検証③と同じなので、画像処理ありの検証は、検証④の理由から行いませんでした。

**ここまでの結果だと、ファイルの存在確認をしていなければ、バリデーションのfileは無意味。**

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

#ファイルの存在を確認しての追加検証
##検証① バリデーションrequiredとfileのみ記載 画像に対する処理なし 
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'required|file',
        ]);
        return redirect('/file')->with('success', '登録完了しました。');
    }
```

**結果:問題なし。バリデーションのエラーがきちんと表示される。**

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証② バリデーションrequiredとfileのみ記載 画像に対する処理あり
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'required|file',
        ]);

        $path = $request->img->store('public/images');
        $filename = basename($path);

        $data = new FileImage;
        $data->file_name = $filename;
        $data->save();

        return redirect('/file')->with('success', '登録完了しました。');
    }
```

**結果:問題なし。バリデーションのエラーがきちんと表示される。**
requiredがしっかり動作しているので、ファイルが無い場合も問題無し。

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証③ hasFileとisValidメソッドのみ記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        if ($request->hasFile('img')) {
            if ($request->file('img')->isValid()) {
                return redirect('/file')->with('success', '登録完了しました。');
            }
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:問題なし。「失敗しました」が表示される。**
ファイルの存在確認を行っているので、Call to a member function isValid() on nullのエラーは発生しませんでした。

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

ちなみに、ネストさせず早期リターンさせるなら以下の記述でも同様の結果になります。
※「!」が入るとわかりづらい人もいるかと思うので、ネストさせる方で記載してます。

```PHP
    public function store(Request $request)
    {
        if (!$request->hasFile('img')) {
            return redirect('/file')->with('success', '失敗しました。');
        }

        if ($request->file('img')->isValid()) 
            return redirect('/file')->with('success', '登録完了しました。');
        }
    }
}
```

##検証④ hasFileとisValidメソッドのみ記載 画像に対する処理あり
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        if ($request->hasFile('img')) {
            if ($request->file('img')->isValid()) {
                $path = $request->img->store('public/images');
                $filename = basename($path);

                $data = new FileImage;
                $data->file_name = $filename;
                $data->save();

                return redirect('/file')->with('success', '登録完了しました。');
            }
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:問題なし。「失敗しました」が表示される。**
ファイルの存在確認を行っているので、Call to a member function isValid() on nullのエラーは発生しませんでした。

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証⑤ バリデーションrequiredとfileとisValidメソッドを記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'required|file',
        ]);

        if ($request->file('img')->isValid()) {
            return redirect('/file')->with('success', '登録完了しました。');
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:問題なし。バリデーションのエラーがきちんと表示される。**
requiredがしっかり動作しているので、ファイルが無い場合も問題無し。

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。

##検証⑥ バリデーションrequiredとfileとisValidメソッドを記載 画像に対する処理あり
検証④で問題がない為、検証の必要がないのでこの検証は行いませんでした。
結果は検証④と同じです。

##検証⑦ バリデーションfileとhasFileとisValidメソッドを記載 画像に対する処理なし
上記の条件で、ファイルを選択しない(空:null)状態で送信ボタンをクリック。

```PHP
    public function store(Request $request)
    {
        $this->validate($request, [
            'img' => 'file',
        ]);

        if ($request->hasFile('img')) {
            if ($request->file('img')->isValid()) {
                return redirect('/file')->with('success', '登録完了しました。');
            }
        }
        return redirect('/file')->with('success', '失敗しました。');
    }
```

**結果:問題なし。「失敗しました」が表示される。**
ファイルの存在確認を行っているので、Call to a member function isValid() on nullのエラーは発生しませんでした。

バリデーションfileはここではザルの状態なので、この記述であれば検証③の結果も踏まえて**fileは不要と言えます**

####★注意点★
同じ条件でファイルを選択した場合は、問題無く動作し、「登録完了しました。」が表示されます。
**結果が見えているので、「画像に対する処理あり」は検証しません。**
結果は同じになります。


#検証結果まとめ

・fileもisValidも、単体の使用では意味がない。
・ファイルの存在確認が必須。
・存在確認をしない場合、isValidはエラーが出るが、fileはエラーもでずザルなので不要だと思う。※断定できない点があります。
・バリデーションでrequiredとfileを設定するなら、isValidは不要だと思う。※断定できない点があります。
・メソッドでhasFileとisValidを使うなら、fileは不要だと思う。※断定できない点があります。

#断定できない点について
「不要。」と断定で記載したのですが、自分の現知識では検証できない内容がある為、断定ができません。
検証過程で、fileもisValidもファイルの存在確認ではなく、**アップロードの処理がされたがどうかを確認していることがわかりました。**

自分が検証できない内容ですが、**ファイルをきちんと選択して送信をした場合で、アップロード時に何らかの障害がでた場合のそれぞれの検証です。**

アップロード時に、わざと障害を起こさせることができませんでした。

仮に起こせたとして、fileもisValidも同じようにエラーを返すなら、片方だけでいいと思います。
もし、それぞれ違う結果がでるなら、両方の記載が必要。
※ただ、バリデーションでエラーを返すのと、メソッドでエラーを返すのどっちがいいか迷いますね。

#結論
**懸念材料がある為、両方記載するのが現状望ましい。**
存在の確認についてはrequiredかhasFileのどちらかでいい。
ただし、hasFileを使用するのであれば、存在確認をしないfileはザルなので、fileは不要。

**素人の個人的な見解なので、参考程度にしておいてください。**