初めてのLaravel ⑩。画像をアップロードしよう

初めてのLaravel ⑩。画像をアップロードしよう
この記事はこんな悩みを解決します
  • 画像のアップロードの仕方が知りたい
  • 画像を保存して取り出せるようにしたい
  • Laravelについて勉強したい

ブログだったり、SNSだったりとWebアプリの中で画像をアップロードして使いたいという場合がると思います。文字で書くよりも効率的に情報が伝わるので画像を扱えるようになるとユーザーに喜ばれます。

この記事を読む事でLaravelでの画像のアップロードの方法が分かります。

初めてのLaravel ⑩。画像をアップロードしよう

今回は画像を選択してサーバーに保存して行く方法を紹介します。まずは画像をアップロードする方法を確認しましょう。

画像をアップロードする方法

画像をアップロードするにはformを使って行きます。

<form action="{{ url('/upload', $user->id) }}" method="POST" enctype="multipart/form-data">
		@csrf
	    <!-- アップロードした画像。なければ表示しない -->
	    @isset ($filename)
	    <div>
	        <img src="{{ asset('storage/' . $filename) }}">
	    </div>
	    @endisset
	    <!-- エラーがあれば表示する -->
	    @if ($errors->any())
		 <div class="alert alert-danger">
		    <ul>
		    @foreach ($errors->all() as $error)
		        <li>{{ $error }}</li>
		    @endforeach
		    </ul>
		 </div>
		 @endif

	    <label for="photo">画像ファイル:</label>
	    <input type="file" class="form-control" name="picture">
	    <input type="submit" value="Submit">
	</form>
画像をアップロードする時のポイント
  • formの属性にenctype=”multipart/form-data”を記入し、ファイルを認識するようにする
  • inputの属性にtype=”file”を記入し、ファイルをアップロードできるようにする
  • @issetでファイルがあれば表示する仕組みにする
  • @ifでエラーがあれば表示する仕組みにする

保存した画像ファイルを呼び出すときには@issetを変えて行きますが、とりあえずアップロードしたファイルが呼び出せるように設定して行きます。

web.phpの設定

/uploadにアクセスするように書いたのでweb.phpを編集して行きます。

Route::post('/upload/{id}', 'PictureController@store');

では、PictureControllerを作って編集して行きます。
まずはTerminalでPictureControllerを作成します。

コントローラーの設定

mbp:training user$ php artisan make:controller PictureController
Controller created successfully.

うまくいったのでPictureControllerを編集して行きます。

public function store($id, Request $request){
      $this->validate($request, [
            'picture' => 'required|file|image|mimes:jpeg,png,jpg,gif|max:1148'
            ]);
    	if ($request->file('picture')->isValid([])) {
            $path = $request->file('picture')->store('public');
            $user = User::find($id);
            return view('comment')->with([
            	'filename' => basename($path),
            	'user' => $user,
            ]);
        } else {
            return redirect()
                ->back()
                ->withInput()
                ->withErrors(['file' => '画像がアップロードされていないか不正なデータです。']);
        }
    }
画像ファイルをアップロードする時のポイント
  • バリデーションを行い、アップロードされたファイルが画像か確認します。
  • ifの条件ではfileメソッドでアップロードされたファイルにアクセスし、isValidメソッドで正常にアップロードされたかを確認します。
  • 正常に行われたらstoreメソッドで保存します。
  • 保存したときにはパスの情報もpathに入っているのでファイルを呼び出すときにはbasenameを使ってファイル名のみ呼び出します。
  • アップロードがうまくいってないのであれば送信したデータをセッションに入れて元のページに戻るように設定します。

これで画像ファイルが保存できるようになりました。
ちなみに保存される場所は下記のpublicディレクトリになっています。

─── strage
    ├── app
         ├── public

あとはTerminalからstorageを公開できるように設定するだけです。

画像ファイルへのリンクの設定

mbp:training user$ php artisan storage:link
The [/Users/user/code/training/public/storage] link has been connected to [/Users/user/code/training/storage/app/public].
The links have been created.

これでpublic/storage から storage/app/public へシンボリックリンクが張られ、Webからアクセスできるようになります。

確認

では、確認して行きます。

ファイルを選択、から画像ファイルを選択しSubmitをクリックします。

うまく画像がアップロードされました。

「The file failed to upload.」と表示される場合

上記の通りに設定しても「The file failed to upload.」と表示される場合があります。原因はphp.iniでアップロードできる上限を決めているからです。

私もmaxで指定しているファイルより小さいのに、このエラーメッセージが出る理由がわかりませんでした。

解決手順①:php.iniの場所を調べる
 /usr/local/etc/php/7.3/php.ini

この呪文で私の場合は「/usr/local/etc/php/7.3/php.ini」が出て来ました。

解決手順②:viコマンドで編集する

①で見つけたパスを使ってviコマンドからphp.iniを編集します。

vi /usr/local/etc/php/7.3/php.ini

編集画面に行けたら「post_max_size」を好きな数字に編集しましょう。初期設定はセキュリティの観点から2MBになっているようです。最近の画像は容量が多いのでこの作業が必須かもですね。

編集が済んだら「:wq」で終了しましょう。

画像のリサイズ

アップロードされる画像のサイズが統一されていないと、呼び出したときにガタガタになり見栄えが悪くなります。

アップロード時にリサイズしておくと呼び出した後の使い勝手が良くなります。

リサイズ手順①:composerからIntervention imageを入れる
composer require intervention/image

Intervention Imageを入れるとリサイズするコマンドが使えるようになります。

リサイズ手順②:PictureControllerを編集する

PictureControllerを編集して使えるようになったコマンドを使用します。

// アップロードファイルを取得
$picture = $request->file('picture');
// 一意のファイル名を作成
$filename = now()->format('YmdHis').uniqid('', true). "." . $picture->extension();//日付にidをつけて元々のファイル形式を使うように指定しています。
// 画像を読み込む
$image = \Image::make($picture);
// Orientation勝手に縦横変えないようにする
$image->orientate()->resize(300, 300,//第一引数で縦、第二引数で横のサイズを指定
  //以下は参考程度
  function ($constraint) {
    // 縦横比を保持したままにする
    $constraint->aspectRatio();
    // 小さい画像は大きくしない
    $constraint->upsize();
  }
);
// 保存する
$image->save(storage_path(). "/app/public/{$filename}");

Intervention Imageを使用する時のポイント

  • \Image::makeでIntervention imageを使えるようにします。
  • orientateやresizeを使って画像を編集します。
  • 保存先のパスをstorage_path(). “/app/public/{$filename}”で指定します。(storeではなくsaveに変わってます。)

これで画像のリサイズと保存が完了です。

プレビューの表示

画像を保存する前にプレビューで表示する方法も解説します。
プレビューにはJavaScriptを使います。

<script>
    // 画像が選択される度に、この中の処理が走る
    $('#icon').on('change', function (ev) {
        // FileReaderが画像を読み込む上で大切
        const reader = new FileReader();
        // 画像が読み込まれた時の動作
        reader.onload = function (ev) {
            $('#icon_img_prv').attr('src', ev.target.result);
        }
        reader.readAsDataURL(this.files[0]);
    })
</script>

idがiconのタグで指定したファイルが変更されると、上記がよび出される仕組みになっています。new FileReader()で画像を読み込んでidがicon_img_prvのタグに表示されます。

このJavaScriptが機能するようにViewでidを指定します。。

<form action="{{ url('/upload', $user->id) }}" method="POST" enctype="multipart/form-data">
		@csrf
	    <!-- アップロードした画像。なければ表示しない -->
	    @isset ($filename)
	    <div>
	        <img id="icon_img_prv"👈追加 src="{{ asset('storage/' . $filename) }}">
	    </div>
	    @endisset
	    <!-- エラーがあれば表示する -->
	    @if ($errors->any())
		 <div class="alert alert-danger">
		    <ul>
		    @foreach ($errors->all() as $error)
		        <li>{{ $error }}</li>
		    @endforeach
		    </ul>
		 </div>
		 @endif

	    <label for="photo">画像ファイル:</label>
	    <input id="icon"👈追加 type="file" class="form-control" name="picture">
	    <input type="submit" value="Submit">
	</form>

これで画像を保存する前にプレビューできるようになります。

保存した画像を呼び出す方法

画像をアップロードできましたが、これではページを移動して戻って来た時に表示されません。そのため、usersテーブルにpictureカラムを作ってそこから画像を呼び出すように設定して行きます。

カラムの設定

まずはusersテーブルにpictureカラムを追加します。

mbp:training user$ php artisan make:migration add_picture_users
Created Migration: 2020_07_10_074117_add_picture_users

では、カラム追加のためのマイグレーションファイルを編集して行きます。

public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('picture', 100);
        });
    }

こんな感じでいいかと思います。
では、マイグレーションを実行します。

mbp:training user$ php artisan migrate
Migrating: 2020_07_10_074117_add_picture_users
Migrated:  2020_07_10_074117_add_picture_users (0.04 seconds)

うまく行きました。
TablePlusで確認します。

うまくできてますね。
では、画像ファイルの保存をこのカラムにするためにPictureController.phpを編集して行きます。

コントローラーの設定

public function store($id, Request $request){
    	$this->validate($request, [
            'picture' => 'required|file|image|mimes:jpeg,png,jpg,gif|max:1148'
            ]);
    	if ($request->file('picture')->isValid([])) {
            $path = $request->file('picture')->store('public');
            $user = User::find($id);
            $user->picture = basename($path); //追加
            $user->save(); //追加
            return view('comment')->with([
              'filename' => basename($path), //削除
             'user' => $user,
            ]);

        } else {
            return redirect()
                ->back()
                ->withInput()
                ->withErrors(['file' => '画像がアップロードされていないか不正なデータです。']);
        }

これでusersテーブルのpictureに保存されるのでビューの$userから画像ファイルが取れるはずです。
なので、ビューも編集して行きます。

ビューの設定

@isset ($user->picture)
	   <div>
	       <img src="{{ asset('storage/' . $user->picture) }}">
	   </div>
@endisset

$filenameではなく、$user->pictureで画像ファイルをとって来ています。

確認

では、確認してみましょう。

ファイルを選択してSubmitをクリックします。

うまく行きました。さらに、他のページに行って戻って来ても画像が表示されているようになっています。

まとめ

今回はデータベースに画像を保存して呼び出す方法を紹介しました。

画像がアップロードできれば、あとは基本通りに行えば大丈夫ですね。過去の記事を参考にしてもらえれば問題ないかと思います。

関連記事