LoginSignup
104
102

More than 3 years have passed since last update.

Laravelで画像ファイルを保存したい

Posted at

今参加しているプログラミングスクールでの課題で、LaravelでECsiteを作っているのですが、Laravelへの画像の保存に苦戦したので、備忘録ようにメモします

作りたいもの

  • Ecsite(の一部)
  • 商品の一覧を表示した際に、画像ファイルが保存されるようにしたい

どこに画像を保存するか?

初めはDBにそのまま画像をぶち込もうと思っていたのですが、どうもそれはスマートではないらしい。。。
terateil:データベースに画像を保存するのはありでしょうか?

すると、LaravelではStorage/app/public内に保存するのが基本のようです。そして、各々の画像へのパスをDBに保存することにしました。

保存するフォームを作る

今まで、お問い合わせフォームを作っていたので、それを使いまわして画像アップロード画面のひな形を作ります。bladeについては省略です。
具体的には、input(入力画面)->confirm(確認画面)->complete(完了画面)の形にして、input->confirmの際に一度Storage/app/public内のtempディレクトリに仮保存し、confirm->completeの際にStorage/app/public内のproductImageディレクトリに移動します。

web.php
Route::get('/image_input', 'ImageController@getImageInput');
Route::post('/image_confirm', 'ImageController@postImageConfirm');
Route::post('/image_complete', 'ImageController@postImageComplete');
image_input.blade.php
@section('body')
    <form action="image_confirm" method="post" enctype="multipart/form-data" id="form">
        @csrf
        ファイル
        <input type="file" name="imagefile" value=""/><br /><br />

        商品名<br />
        <input type="text" name="product_name" size="50" value="{{ old('name') }}"/><br /><br />

        <input type="submit" name="confirm" id="button" value="確認" />
    </form>
@endsection

formタグ内で

enctype="multipart/form-data

を指定しないと、controllerでfileメソッドを使ってファイルを取り出そうとしてもnullになります。

image_confirm.blade.php
@section('body')
    <form action="image_complete" method="post">
        @csrf
        <table border="1">
            <tr>
                <td>画像</td>
                <td><img src="{{ $data['read_temp_path'] }}" width="200" height="130"></td>
            </tr>
            <tr>
                <td>商品名</td>
                <td>{{ $data['product_name'] }}</td>
            </tr>
        </table>
        <input type="submit" name="action" value="送信" />
    </form>
@endsection
@section('body')
    <p>商品のアップロードが完了しました</p>
@endsection

また、DBは次のmigrationファイルを基にして作り、Modelも作成しておきます

xxxx_xx_xx_xxxxxx_create_products_table.php
public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('product_id');
            $table->string('path');
            $table->string('product_name');
            $table->timestamps();
        });
    }

問題はcontroller...

取り敢えず、フォームから画像と名前を受け取って、画像には一意のファイル名としてtempディレクトリに保存します。また、sessionに保存して、confirm画面から読み込めるようにします。

ImageController.php
public function getImageInput(){
    return view('image_input');
}

public function postImageConfirm(ImageUploadRequest $request){
    $post_data = $request->except('imagefile');
    $imagefile = $request->file('imagefile');

    $temp_path = $imagefile->store('public/temp');

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);

    return view('image_confirm', compact('data') );

storeメソッドは、引数内のディレクトリに一意のファイル名として保存し、そこへのパスを返します。

画像を読み込むディレクトリ

Laravelの場合、画像を読み込むディレクトリはpublic/storageディレクトリです。
ややこしいのでもう一度書きます。
保存:storage/app/public
読込:public/storage
名前を変えてほしいですね。なので、public/storageから、storage/app/public内を読み込めるよう(シンボリックリンクを張る)にします。

php artisan storage:link

これで、storage/app/public内を覗けるようになりました。

ディレクトリが違えばパスも違う

では、いざconfirm画面を表示! させても画像が表示されません...
それもそのはず、読み込みに行っているパスが
(storage/app/)public/temp/xxx.jpeg
となっているからですね。でも先ほど書いた通り、読み込むのは
(public/)storage/temp/xxx.jpeg
でないといけないですね。
よって、controllerを次のように変えます。

ImageController.php
    $temp_path = $imagefile->store('public/temp');
    $read_temp_path = str_replace('public/', 'storage/', $temp_path); //追加

    $product_name = $post_data['product_name'];

    $data = array(
        'temp_path' => $temp_path,
        'read_temp_path' => $read_temp_path, //追加
        'product_name' => $product_name,
    );
    $request->session()->put('data', $data);

str_replaceメソッドで、public/をstorage/に置き換えました。
いざ表示させると、無事できました!

後は移動させてDBへ保存

次に、confirm->completeにおいて、tempディレクトリからproductImageディレクトリに移動させ、DBに保存します

public function getImageComplete(Request $request) {
    $data = $request->session()->get('data');
    $temp_path = $data['temp_path'];
    $read_temp_path = $data['read_temp_path'];

    $filename = str_replace('public/temp/', '', $temp_path);
    //ファイル名は$temp_pathから"public/temp/"を除いたもの
    $storage_path = 'public/productimage/'.$filename;
    //画像を保存するパスは"public/productimage/xxx.jpeg"

    $request->session()->forget('data');

    Storage::move($temp_path, $storage_path);
    //Storageファサードのmoveメソッドで、第一引数->第二引数へファイルを移動

    $read_path = str_replace('public/', 'storage/', $storage_path);
    //商品一覧画面から画像を読み込むときのパスはstorage/productimage/xxx.jpeg"
    $product_name = $data['product_name'];

    $this->productcontroller->path = $read_path;
    $this->productcontroller->product_name = $product_name;
    $this->productcontroller->save();
    return view('image_complete');
}

無事、画像が保存されました!
後は読み込むところで

<img src="{{ $path }}" width="200" height="130">

とすればOKです!

参考にしたサイト

Laravel学習帳:画像アップロード(基本)
Qiita:Laravel5.6でファイルアップロードの実装と躓いたところまとめ
Qiita:[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ(画像の保存場所は?シンボリックリンクって?)

104
102
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
102