Laravelで画像をアップロードし、Webページで使用する方法

今回の記事では

  1. 画面から画像をアップロード
  2. アップロードした画像をLaravelプロジェクトのディレクトリに保存
  3. ブラウザからアップロードした画像にアクセス(ブラウザに画像を表示)

をやっていきます。

画像をアップロード

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>画像テスト</title>
</head>
<body>
    <form method="post" action="{{route('image_test_post')}}" enctype="multipart/form-data">
        @csrf
        <input type="file" name="img">
        <button>送信</button>
    </form>
</body>
</html>

bladeファイルを作成します。
基本はよくあるformタグですが、「enctype=”multipart/form-data”」を忘れずに設定しましょう。
これを忘れてしまうと、ファイルのアップロードが行えません。

コントローラ、ルートは以下のような形で定義。
ひとまず画面の表示まで。

Route::get('/image_test', [ImageTestController::class, 'index'])->name('image_test');
Route::post('/image_test', [ImageTestController::class, 'imagePost'])->name('image_test_post');
/**
 * 画面表示
 */
public function index(){
    return view('image_test');
}

これで、「/image_test」にアクセスすると、画像をアップロードするための画面が表示されます。

それではアップロード後の処理を作成していきます。
作成したのは「imagePost」メソッド。
useしないと動かない処理もあるので、useを忘れないように。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;

class ImageTestController extends Controller
{
    /**
     * 画面表示
     */
    public function index(){
        return view('image_test');
    }

    /**
     * 画面表保存
     */
    public function imagePost(Request $req){
        $file = $req->file('img');
        $file_path = $file->store('public');
        Session::put('img_path', $file_path);
        return redirect()->route('image_test');
    }
}

Requestからfileメソッドを使用してファイルを取得します。
fileメソッドで指定するのはinputのname属性です。

取得したfileインスタンスのstoreメソッドでLaravelプロジェクトのstorageディレクトリに保存します。
storeメソッドの引数でディレクトリを指定すると、storage以降のディレクトリを指定できます。
この際、対象のディレクトリが存在しない場合はLaravelの方でディレクトリの作成を行ってくれます。(とても便利)
ファイルはサーバにアップロードされたタイミングでファイル名が変更されます。
このファイル名も含めて、storeメソッドの返却値で取得可能です。

今回は取得したファイルパスをセッションに格納しておきます。
セッションに格納した値を画面で表示するために、image_test.blade.phpにセッション値の表示を追記します。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>画像テスト</title>
</head>
<body>
    <form method="post" action="{{route('image_test_post')}}" enctype="multipart/form-data">
        @csrf
        <input type="file" name="img">
        <button>送信</button>
    </form>
    {{session('img_path')}}
</body>
</html>

アップロードした画像をブラウザから表示

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

アップロードした画像をブラウザから表示するためには一工夫が必要になります。
Laravelでは「public」ディレクトリをブラウザに公開しており、それ以外のディレクトリにファイルがあってもブラウザからアクセスできません。
ちなみに、このpublicというのは「storage/public」ではなく「public」です。
storageの中ではなく、アプリケーションの最上位ディレクトリにも「public」があります。
こちらが、ブラウザに公開されるディレクトリです。

今回アップロードしたファイルはstorageにあるので、publicからstorageにアクセスできるようする必要があります。
シンボリックリンクというものを作成すると、publicからstorageにアクセスできるため、publicからstorageへのシンボリックリンクを作成します。
シンボリックリンクは通常のリンクが移動するためのものであるのに対して、その場からファイル本体にアクセスができるリンクです。
移動せずにファイルにアクセスができるため、publicディレクトリからstorage下の画像にアクセスができるというわけですね。
(まるで、publicディレクトリに本体(画像)が配置されているかのように使用できます)
Laravelはコマンドが用意されているため、コマンドを実行すればOKです。

php artisan storage:link

このコマンドを実行すると、publicディレクトリに「storage」というシンボリックリンクが作成されます。
このstorageというシンボリックリンクにアクセスすると「storage/public」の中身が表示されます。
これで使用準備はOK。

ブラウザからアクセス

今回は画像なので、imgタグを使用します。
このimgタグのsrc属性の値がサーバにアップロードした画像になれば、アップロードした画像を使えるわけですね。
とりあえず、セッションの値をそのまま入れてみましょう。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>画像テスト</title>
</head>
<body>
    <form method="post" action="{{route('image_test_post')}}" enctype="multipart/form-data">
        @csrf
        <input type="file" name="img">
        <button>送信</button>
    </form>
    <img src="{{session('img_path')}}" alt="">
</body>
</html>

それでは画面をリロードして画像をアップロード。
。。。残念ながら画像は表示されません。

src属性の値を確認すると
public/sQliw7ki3AuFviWjdFKvQVwbyvUrbHW0QeqfkAgW.jpg
となっています。これではアクセスできません。URLの形式になっていないからですね。
LaravelでpublicディレクトリのリソースにアクセスするためのURLを「asset」メソッドによって生成できます。
そのため、今回のソースは

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>画像テスト</title>
</head>
<body>
    <form method="post" action="{{route('image_test_post')}}" enctype="multipart/form-data">
        @csrf
        <input type="file" name="img">
        <button>送信</button>
    </form>
    <img src="{{asset(session('img_path'))}}" alt="">
</body>
</html>

こんな形になります。
さて、もう一度アップロード・・・

まだ画像が表示されません。。。

src属性の値を取得すると・・・
http://127.0.0.1:8000/public/Y2L5JOS9hTB7E58DzElvlxBvrjQxK2DqanUFjQpT.jpg
となっています。

サーバのアドレス部分(http://127.0.0.1:8000)は環境によって異なりますが、サーバアドレスでpublicディレクトリにアクセスできます。
そのため、今回の画像へのアクセスパスは「http://127.0.0.1:8000」に「public」ディレクトリ以降のパス(storage/Y2L5JOS9hTB7E58DzElvlxBvrjQxK2DqanUFjQpT.jpg)を付与した「http://127.0.0.1:8000/storage/Y2L5JOS9hTB7E58DzElvlxBvrjQxK2DqanUFjQpT.jpg」が正解となります。

sessionに格納しているパスをちょっと書き換えてあげましょう。

/**
 * 画面表保存
 */
public function imagePost(Request $req){
    $file = $req->file('img');
    $file_path = $file->store('public');
    Session::put('img_path', str_replace('public', 'storage', $file_path));
    return redirect()->route('image_test');
}

これでもう一度画像をアップロードしてあげれば、画像が表示されます。
str_replaceで都合の良い形にパスをstoreメソッドの返却値のパスを書き換えているわけですね。

今回はセッションにパスを格納していますが、この値をDBに保存することでユーザのプロフィール画像の登録などができるわけです。
(DBに画像パスを保存して、画面表示の際にassetで画像のURLに変換して表示する形ですね)