今参加しているプログラミングスクールでの課題で、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ディレクトリに移動します。
Route::get('/image_input', 'ImageController@getImageInput');
Route::post('/image_confirm', 'ImageController@postImageConfirm');
Route::post('/image_complete', 'ImageController@postImageComplete');
@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になります。
@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も作成しておきます
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画面から読み込めるようにします。
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を次のように変えます。
$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] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ(画像の保存場所は?シンボリックリンクって?)