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

Laravel5.6でファイルアップロードの実装と躓いたところまとめ

More than 1 year has passed since last update.

業務でも自習でも初めてファイルアップロード機能を手を動かして実装してみたので手順をまとめておこうと思います。
実装中に躓いた箇所があったのでその対応方法も記載します。

動作環境について

OS:macOS High Sierra
version: 10.13.6
Docker(Engine):18.09.1

対象ブラウザ

GoogleChrome:72以上

環境

centOS:7.5
Laravel:5.6
PHP:7.2
MySQL:5.7
PostgreSQL:10.6(MySQLから変更しました。)
APache2.4

参考資料

Laravelで画像ファイルアップロードをする簡単なサンプル
【Laravel5.6】画像ファイルアップロードについてのポイントまとめ
Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。

画面構成について

全然スマートな画面じゃなくて恐縮ですが・・・。
管理画面にある各商品情報の右に「画像」ボタンを配置しています。

スクリーンショット 2019-02-11 12.04.58.png

「画像」ボタンを押下するとファイルアップロードのモーダルが表示されます。

スクリーンショット 2019-02-11 12.05.18.png

アップロードに成功するとTOPページに各商品ごとの画像が表示される様になります。
*「-------」となっている商品はまだ画像のアップロードを行っていないことを示しています。

スクリーンショット 2019-02-11 12.07.58.png

下記では、この上記画像の動作を実現する方法をまとめています。

View

モーダルのformタグ内は下記の通りになっています。
name属性が「select_id」のinputタグは「画像」ボタン押下時にjavascriptでvalueが入る様になっています。

<form method="POST" action="{{ route('admin_upload_image') }}" enctype='multipart/form-data'>
    {{ csrf_field() }}
    <input type="hidden" name="select_id" value="">

    <div class="form-group row">
        <label for="goods_image" class="col-md-3 col-form-label text-md-left">{{ __('イメージ') }}</label>

        <div class="col-md-7">
            <input type="file" name="goods_image" value="" style="border:none;" >
            <small class="input_condidion">*jpg,png形式のみ</small></br>
            <small class="input_condidion">*最小画像サイズ:縦横100px</small></br>
            <small class="input_condidion">*最大画像サイズ:縦横600px</small>

            @if ($errors->has('goods_image'))
                <span class="invalid-feedback" role="alert">
                    <strong>{{ $errors->first('goods_image') }}</strong>
                </span>
            @endif
        </div>
    </div>

    <div class="col-md-offset-3 text-center">
        <button type="button" class="btn btn-primary" data-dismiss="modal">閉じる</button>
        <input type="submit" class="btn btn-success" value="登録">
    </div>
</form>

ルーティング

web.phpに下記の通りに記載しています。

Route::group(['prefix' => 'admin'], function(){
.
.
.
    Route::post('goods/upload', 'Admin\HomeController@uploadGoodsImage')->name('admin_upload_image');
});

Controller

Controllerの処理の前にバリデーションファイルを別途用意する予定でしたが、後述の躓いた箇所の影響もあり、他の人が書いてくださった内容に習わせて頂きました。
拡張子の指定が「mimes」、画像サイズの指定が「dimensions」をキーとして詳細を指定しています。

・画像の格納先を指定するstoreAs()について
⇨第1引数:保存するディレクトリ名,第2引数:アップロードするファイル名,第3引数:disksのドライバ名(config/filesystems.phpに記載)となっています。
今回の場合はgoodsディレクトリの中に各商品IDのディレクトリを作成し、その中にアップロードした時の同様のファイル名で格納する様にパラメーターを変数で指定しています。
第3引数は特に変更がなければ「public」のままで良いと思います。
変更する場合は「config/filesystems.php」を編集する必要があります。

public function uploadGoodsImage(Request $request)
{
    // 選択した商品IDの取得
    $select_id = $request->input('select_id');
    // アップロードしたファイル名を取得
    $upload_name = $_FILES['goods_image']['name'];

    // アップロードするディレクトリ名を指定
    $up_dir = 'goods/' . $select_id;

    // アップロードしたファイルのバリデーション設定
    $this->validate($request, [
        'goods_image' => [
            'required',
            'file',
            'image',
            'mimes:jpeg,png',
            'dimensions:min_width=100,min_height=100,max_width=600,max_height=600',
        ]
    ]);

    //アップロードに成功しているか確認
    if ($request->file('goods_image')->isValid([])) {
        $filename = $request->file('goods_image')->storeAs($up_dir, $upload_name, 'public');

        // DBへファイル名登録処理
        $goods = \App\Models\Goods::findOrFail($select_id);
        // $filenameだとパスが含まれてしまう為、basename()で囲う
        $goods->image_name = basename($filename);
        // 更新(差分があればDBに登録)
        $goods->save();

        return redirect()->to('admin/home')->with('flashmessage', 'イメージ画像の登録が完了しました。');
    }
    else{
        return redirect()->to('admin/home')->with('flashmessage', 'イメージ画像の登録に失敗しました。');
    }
}

シンボリックリンクの作成

下記コマンドで public/storage から storage/app/public にシンボリックリンクを貼ること出来ます。

[root@87c2be02241a shop]# php artisan storage:link
The [public/storage] directory has been linked.

画像アップロードの確認

各商品のIDを格納先として画像が保存されていることが確認出来ます。

[root@87c2be02241a shop]# ls public/storage/goods/4
sample_goods.png
[root@87c2be02241a shop]# ls storage/app/public/goods/4
sample_goods.png

アップロードするファイルを表示させる時

各商品の画像名を取得して、nullでは無い場合はURLを指定して表示させています。

@if($goods->image_name)
    <img src="{{ asset('storage/goods/' . $goods->id . '/' . $goods->image_name) }}" width="40" height="40" alt="no_goods_image" />
@else
    <p>--------</p>
@endif

躓いたところ

アップロードするとhome画面にリダイレクトしてしまう不具合が発生。
バリデーションで何かミスかなと調べてみました。

Controller部分内でdd()を使ってファイルが取得出来ているかを確認してみると、ファイルを取得出来ていませんでしrた。

本来下記の形でファイルを取得出来るのですが、nullになるばかり。$_FILESを確認してもアップロードされたファイルが無い状態でした。

$file = $request->file('goods_image');
dd($file)

調べていく中でformタグに「enctype」属性が抜けていたのを気づいたので下記の通り追記してみたのですが、これもだめでした。

enctype="multipart/form-data"

php.iniの設定が悪いのかと思い確認してみました。
ファイルアップロード関連で関係のありそうなのは下記の設定ですが確認してみるとアップロードしているファイルのサイズ的には問題無さそうでした。

upload_max_filesize = 10M
file_uploads = On
max_file_uploads = 20
post_max_size = 8M

⇨念の為「post_max_size」と「upload_max_filesize」の上限を上げてみたのですがこれも結局だめでした。

その後調べを進めてみるとやはり「enctype="multipart/form-data"」が記載されていないのが原因とする記述が多かった為、もしかしてブラウザ上では表示されていないのかなと思いデベロッパーツールで確認してみると、原因がわかりました。

「enctype="multipart/form-data"」と表示されるべきところが下記の様に表示されていました。

enctype=""multipart/form-data""

そこでviewファイルにて「enctype='multipart/form-data'」と書き直したところ、無事ファイルアップロードが出来ました。

後日改めてダブルクォーテーションで囲ってみたらブラウザでも「enctype="multipart/form-data"」と表示されていました。
viewファイルの構成を考えながら作っていた為、viewファイルのどこかで記述がミスがあったと思われます。
viewとファイルアップロードを同時平行で作っていたのがアダとなった形です。

取り急ぎでファイルアップロードの機能を試したい時に正常に動かない場合、上記対応で動かせることがわかりました。

maejima_f
PHPでWeb開発をしているエンジニアです。 自分用のメモとしてまとめています。
rosso-tokyo
渋谷にある少数精鋭のベンチャーSierです。最新技術・手法は常にキャッチアップし社内勉強会で情報共有しています。最近では外部技術セミナーも開催し情報発信の幅を広げています。
http://www.rosso-tokyo.co.jp
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
Comments
No 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
ユーザーは見つかりませんでした