見出し画像

画像のアップロード機能 #Laravelの教科書

はじめに

本稿ではLaravelを利用して画像のアップロード機能を作成する方法について解説していきます。

※ Laravel8で動作確認していますが、Laravel7.xでも動作する内容です。


■ 課題

・画像のアップロードフォームを作成する
・アップロードした画像を表示する


# 設計

作成するアプリは次のような仕様とします。

1. /formにアクセスするとアップロードフォームが表示される
2. 画像以外をアップロードするとエラーを表示する
3. アップロードに成功すると/listにリダイレクト
4. /listではアップロードした画像が一覧で表示される

アップロードした画像の情報はデーターベースに保存します。usersに保存すればプロフィール画像のように使えますし、掲示板機能に追加する形で行えば画像掲示板のようなものを作ることも可能です。

今回は専用のテーブル/モデルを用意してそこに保存する形とします。

Controllerやモデルは次のように定めます。

・Controller:
 UploadImageController(アップロード処理)
 ImageListController(画像一覧)
・Model:UploadImage
・テーブル:upload_images


# プロジェクトの準備

まずはプロジェクトの準備を行います。create-projectを使ってプロジェクトを作成します。既存のプロジェクトに導入することも可能です。

今回は「upload_image」というプロジェクトを作成します。

composer create-project --prefer-dist laravel/laravel upload_image
cd upload_image

次にDBの設定と作成を行います。こちらについては記述を省略しますが、.envに正しく接続情報を記述してください。

.envの設定例(MAMPの場合)

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=8889
DB_DATABASE=laravel_sample
DB_USERNAME=root
DB_PASSWORD=root
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock

DBの設定が問題なければマイグレーションを実行します。

php artisan migrate

マイグレーションの実行が問題なければ、開発サーバーを起動して動作を確認します。

php artisan serve



# 事前準備

事前準備としてファイルのアップロードが必要なプロジェクトは下記コマンドを実行しておく必要があります。

php artisan storage:link

これによりpublicディレクトリの下にstorageディレクトリのリンクが作成されます。

このコマンドはプロジェクトで一回だけ実行すれば大丈夫です。



# マイグレーションの作成

upload_imageテーブルを作成するためのマイグレーションを作成します。

php artisan make:migration create_upload_image_table

database/migrations以下にマイグレーションファイルが生成されます。

アップロードした画像の情報を保存するupload_imageテーブルには次のカラムを設置します。

1. 元のファイル名:file_name(string、必須)
2. ファイルの保存先:file_path(string、必須)

これら2個のカラムを設定したupload_imageテーブルを作成するためのマイグレーションファイルは次のようになります。

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUploadImageTable extends Migration
{
   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
       Schema::create('upload_image', function (Blueprint $table) {
			$table->id();
			$table->string("file_name");
			$table->string("file_path");
           $table->timestamps();
       });
   }
   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
       Schema::dropIfExists('upload_image');
   }
}


マイグレーションの作成が終わったらマイグレーションを実行します。

php artisan migrate

実行後にエラーが表示されなければテーブルの作成は成功です。

php artisan serve
Migrating: 2020_09_15_120739_create_upload_image_table
Migrated: 2020_09_15_120739_create_upload_image_table (41.27ms)


# モデルの作成

upload_imageと関連させたUploadImageモデルを作成します。

php artisan make:model UploadImage

作成された「app/Models/UploadImage.php」を開いて編集します。Laravel7の場合は「app/UloadImage.php」に作成されます。

app/Models/UploadImage.php

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UploadImage extends Model
{
	use HasFactory;
	
	protected $table = "upload_image";
	protected $fillable = ["file_name","file_path"];
}

◆ ポイント ◆

protected $table = "upload_image";

upload_imageテーブルと連携するために「protected $table」で指定しています。

protected $fillable = ["file_name","file_path","file_size"];

データの保存が楽になるようにfillableを指定しています。


# 画像のアップロードフォームの表示(Controlelrの作成)

画像のアップロードフォームの表示、アップロード処理を行うための「UploadImageController」を作成します。

php artisan make:controller UploadImageController

作成されたUploadImageController.phpを編集します。


app/Http/Controllers/UploadImageController.php

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UploadImageController extends Controller
{
    function show(){
		return view("upload_form");
	}

	function uplopad(Request $request){
		
	}
}

upload_formを表示するshow()関数、画像のアップロードフォームの遷移先のupload()関数を定義します。upload()関数の実装は後述します。



# 画像のアップロードフォームの表示(Viewの作成)

UploadImageControllerで指定したupload_formテンプレートを作成します。

resources/views/upload_form.blade.php


@if (count($errors) > 0)
<div class="alert alert-danger">
	<ul>
		@foreach ($errors->all() as $error)
			<li>{{ $error }}</li>
		@endforeach
	</ul>
</div>
@endif
<form 
	method="post"
	action="{{ route('upload_image') }}"
	enctype="multipart/form-data"
>
	@csrf
	<input type="file" name="image" accept="image/png, image/jpeg">/>
	<input type="submit" value="Upload">
</form>

◆ ポイント ◆

<form 
	method="post"
	action="{{ route('upload_image') }}"
	enctype="multipart/form-data"
>

ファイルのアップロードはPOSTなので「method="post"」を指定しています。

actionは遷移先の指定です。upload()関数が動作するように指定します。

enctype="multipart/form-data"」を<form>に追加することでファイルのアップロードが出来るようになります。


<input type="file" name="image" accept="image/png, image/jpeg" />

ファイルをアップロードするには<input type="file">タグを使います。accept属性を指定することでアップロード出来るものを画像に限定できます。


# ルーティングの設定

routes/web.phpを編集し、アップロードフォームの表示、アップロードフォームの遷移先を登録します。

routes/web.php

Route::get('/form', 
	[App\Http\Controllers\UploadImageController::class, "show"]
	)->name("upload_form");

Route::post('/upload', 
	[App\Http\Controllers\UploadImageController::class, "upload"]
	)->name("upload_image");

それぞれ「upload_form」「upload_image」の名前をつけています。


ルーティングの設定まで終わったら、ブラウザで「http://127.0.0.1:8000/form」を開いてアップロードフォームの表示を確認しましょう。

スクリーンショット 2020-09-15 23.12.27



# アップロード処理の作成

UploadImageControllerのupload()関数を実装していきます。

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\UploadImage;
class UploadImageController extends Controller
{
   function show(){
		return view("upload_form");
	}
	function upload(Request $request){
		$request->validate([
			'image' => 'required|file|image|mimes:png,jpeg'
		]);
		$upload_image = $request->file('image');
	
		if($upload_image) {
			//アップロードされた画像を保存する
			$path = $upload_image->store('uploads',"public");
			//画像の保存に成功したらDBに記録する
			if($path){
				UploadImage::create([
					"file_name" => $upload_image->getClientOriginalName(),
					"file_path" => $path
				]);
			}
		}
		return redirect("/list");
	}
}

◆ ポイント ◆

use App\Models\UploadImage;

UploadImageに保存するため、use句を忘れないように記述します。


$request->validate([
	'image' => 'required|file|image|mimes:png,jpeg'
]);

<input type="file" name="image" />を受け取るためのバリデーションです。

バリデーションは次の意味です。

・required 必須
・file ファイルアップロード形式
・image 画像
・mimes:png,jpeg PNGとJPEGのみ許可


$upload_image = $request->file('image');

<input type="file" name="image" />から渡される値を受け取るにはfile()関数を使います。


$path = $upload_image->store('uploads',"public");

アップロードした画像を保存する処理です。store()関数を呼び出すことでアップロードされた画像(一時ファイル)を「publicストレージ」の「uploadsディレクトリ」に保存します。

画像の保存に成功した場合、$pathには画像のパスが入ります。

store()関数を使った場合、「uploads/JfOlWcxlNl3B2bDcot3YBFuViAweRoMe7kRWrw6z.png」のようなランダムなファイル名に自動的に置き換えられます。

※ ファイル名を指定したい場合はstoreAs()関数を利用


UploadImage::create([
	"file_name" => $upload_image->getClientOriginalName(),
	"file_path" => $path
]);

DBに記録する処理です。

「$upload_image->getClientOriginalName()」でアップロードした元のファイル名が取得出来るので、DBに記録しています。



アップロード処理ができたらアップロードフォームから画像をアップロードして次のことを確認してください

・現時点では、アップロード後にリダイレクトする「/list」はまだ作成していないため、アップロード後はエラー表示となります。
・アップロードした画像が「storage -> app -> public -> upload」に保存されていること

スクリーンショット 2020-09-15 22.31.29


・phpmyadminなどでDBに記録できているか

スクリーンショット 2020-09-15 22.32.34


続いて、アップロードされた画像の一覧機能を作っていきます。


# アップロードされた画像一覧機能の作成(Controller)

アップロードした画像一覧を「/list」で表示出来るように、画像一覧機能を作成していきます。

make:controllerコマンドを使って、ImageListControllerを作成します。

ImageListControllerにはshow()という画像一覧を表示する関数を作成します。

php artisan make:controller ImageListController

作成されたImageListControllerを開いて編集していきます。


app/Http/Controllers/ImageListController.php

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\UploadImage;
class ImageListController extends Controller
{
   function show(){
		//アップロードした画像を取得
		$uploads = UploadImage::orderBy("id", "desc")->get();

		return view("image_list",[
			"images" => $uploads
		]);
	}
}

◆ ポイント ◆

use App\Models\UploadImage;

DBにアクセスするためUploadImageを利用します。use句を忘れないように書きます。


$uploads = UploadImage::orderBy("id", "desc")->get();

orderBy()で新しい順に並べて取り出しています。


view("image_list",[
	"images" => $uploads
]);

image_listのViewを表示します。取り出した画像を「images」というパラメーターで渡しています。



# アップロードされた画像一覧機能の作成(View)

ImageListControllerで指定した「image_list」のViewを作成します。

resources/views/image_list.blade.php

<a href="{{ route('upload_form') }}">Upload</a>
<hr />

@foreach($images as $image)
<div style="width: 18rem; float:left; margin: 16px;">
	<img src="{{ Storage::url($image->file_path) }}" style="width:100%;"/>
	<p>{{ $image->file_name }}</p>
</div>
@endforeach


◆ ポイント ◆

@foreach($images as $image)
...
@endforeach

Controllerから渡された$imagesを繰り返して処理しています。


{{ Storage::url($image->file_path) }}

DBに記録されたアップロードされた画像のパスを表示するには「Storage::url()」を使って変換する必要があります。<img>タグのsrc属性に指定することで画像を表示できます。


Storage:url()によって、次のように「/storage/」を付与してURLに変換してくれます。

(file_pathの値)uploads/dspOjJaCQGlrW0m3KIY0kQzOpg4igmSRnvXCZaR2.jpeg

/storage/uploads/dspOjJaCQGlrW0m3KIY0kQzOpg4igmSRnvXCZaR2.jpeg



# アップロードされた画像一覧機能の作成(ルーティング)

routes/web.phpを編集し、「/list」でImageListControllerのshow()関数が呼び出されるように設定します。

routes/web.php

Route::get('/list', 
	[App\Http\Controllers\ImageListController::class, "show"]
	)->name("image_list");

ルーティングが設定できたらアップロードされた画像が表示されるかブラウザで「http://127.0.0.1:8000/list」を開いて動作確認してみましょう。

画像4

画像が表示されない時は事前準備で行った「php artisan storage:link」のコマンドを実行していない可能性があります。

画像はフリー素材のぱくたそさんからお借りました。

宿題に集中できない小学生の写真素材 
主さまの生着替えを見て、前世の記憶を取り戻した会社員40代男性の写真


# まとめ

画像のアップロードは普通のフォームと似ていますが微妙に異なるポイントがいくつかあります。

一度作ってしまえば他の機能に組み込むことが簡単なので要点とまとめて再利用できるようにしましょう。

・「php artisan storage:link」を忘れないように実行する
・store()関数で画像を保存する
・アップロードした画像は「storage -> app -> public」以下に保存されている
・アップロードした画像を<img>で表示する場合はStorage::url()を使って変換する


おつかれさまでした!

---


完成まで突っ走る意気込みです。サポートしていただけると非常に嬉しいです。応援よろしくお願いします。