この記事は、PHPのフレームワーク「Laravel」で画像ファイルのアップロードをする方法の解説記事です。
私(わや@wayasblog)自身、Laravelを最近勉強し始めたので、かなり丁寧に解説した初心者向けの記事となっています。
コードだけ見たい!という方は、こちら(GitHub)からどうぞ。
今回は「画像アップロード(基本) – Laravel学習帳」を参考に、自分なりに少し変更してみました。
» Laravelで作成したアプリやアウトプットの一覧はこちら
スポンサーリンク
Laravelで画像ファイルを確認/完了画面つきでアップロードするイメージ
スクリーンショットで紹介していきます。
今回は、Bootstrapで簡単にスタイルを作成しました。
トップページ
ページ上部に名前と画像をアップロードするフォームがあり、下部にアップロードした画像が一覧表示されるようになっています。
確認ページ
バリデーション
完了ページ
追加した画像は一覧のトップに表示される
トップページの画像一覧の最上部に表示されました。
初期設定
インストールやお決まりの初期設定をしていきます。
Laravelのインストール
composer create-project --prefer-dist laravel/laravel upload_image "6.*"
「upload_image」というフォルダの中に、Laravelがインストールされました。
phpMyAdimnでDB作成
今回は「upload_image」というDBを作成しました。
.envファイルの設定
DBの設定のため、.envファイルを編集します。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=upload_image
DB_USERNAME=root
DB_PASSWORD=root
configファイルの設定
config/app.php
で日本語と時間の設定をします。
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
以上で、初期設定は終わりです。
コントローラーの作成
コントローラーの役割は、以下の通りです。
- ルートから受け取った情報をモデルに処理をお願いする
- モデルから受け取った情報をビューに表示する
今回の場合は、DBとの接続と処理、バリデーション、画像ファイルの保存等の処理を書いていきます。
php artisan make:controller UploadersController
※Laravelの規約に従うため、コントローラー名は必ず複数形にします。
app/Http/Controllers/UploadersController.php
が生成されました。
ここに、以下のfunction
を作っていきます。
- index:トップページ
- confirm:確認ページ
- complete:完了ページ
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UploadersController extends Controller
{
public function index()
{
return view('uploads.index');
}
public function confirm()
{
return view('uploads.confirm');
}
public function complete()
{
return view('uploads.complete');
}
}
ルーティングの設定
routes/web.php
を編集します。
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// トップページ
Route::get('/', 'UploadersController@index')->name('index');
// 確認ページ
Route::post('/confirm', 'UploadersController@confirm')->name('confirm');
// 完了ページ
Route::post('/complete', 'UploadersController@complete')->name('complete');
ビューの作成
まず、resources/views
にuploadsフォルダを作成し、
- index
- confirm
- complete
以上の3つのbladeテンプレート作成します。
フォームは「Laravel Collective」の「Forms & HTML」を使うので、インストールします。
composer require laravelcollective/html
レイアウトを共通化する
headタグなど、すべてのファイルに共通している部分を、共通化していきます。
resources/views
にlayout.blade.php
を作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>画像アップロード</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>
<body>
<div class="container">
@yield('content')
</div>
</body>
</html>
index.blade.php
@extends
でlayout.blade.php
を継承します。
そして、@section
から@endsection
の中に、テンプレートの@yield('content')
に表示させる部分を書いていきます。
ひとまず、以下のようになりました。
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード</h1>
<div class="container mb-5">
{{ Form::open(['route' => 'confirm', 'method' => 'POST', 'enctype' => 'multipart/form-data']) }}
@csrf
<div class="form-group row">
<p class="col-sm-4 col-form-label">お名前</p>
<div class="col-sm-8">
{{ Form::text('name', null, ['class' => 'form-control']) }}
</div>
</div>
<div class="form-group row">
<p class="col-sm-4 col-form-label">画像</p>
<div class="col-sm-8">
{{ Form::file('image') }}
</div>
</div>
<div class="text-center">
{{ Form::submit('確認画面へ', ['class' => 'btn btn-primary']) }}
</div>
{{ Form::close() }}
</div>
@endsection
confirm.blade.php
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード - 確認画面</h1>
<div class="container mb-5">
{{ Form::open(['route' => 'complete', 'method' => 'POST']) }}
@csrf
<div class="form-group row">
<p class="col-sm-4 col-form-label">お名前</p>
<div class="col-sm-8">
</div>
</div>
<div class="form-group row">
<p class="col-sm-4 col-form-label">画像</p>
<div class="col-sm-8">
</div>
</div>
<div class="text-center">
{{ Form::submit('登録', ['class' => 'btn btn-primary']) }}
</div>
{{ Form::close() }}
</div>
@endsection
complete.blade.php
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード - 完了</h1>
<div class="text-center">
<p>画像を登録しました!</p>
<a href="{{ route('index') }}">トップページへ戻る</a></div>
</div>
@endsection
モデルとマイグレーションの作成
モデルはDBとの連携を行う機能で、マイグレーションはDBを管理する機能です。
php artisan make:model Uploader -m
※Laravelの規約に従うため、モデル名は必ず単数形にします。
app/Uploader.php
が作成されるので、中身を書いていきましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Uploader extends Model
{
protected $fillable = [
'name',
'image',
];
}
name
はお名前、image
は画像ファイル名が入ります。
コマンドで-m
を付けると、database/migrations
にマイグレーションファイルも一緒に作られるので、中身を書いていきます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUploadersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('uploaders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('image');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('uploaders');
}
}
ここまで完了したら、以下のコマンドでDBにテーブルが作成されます。
php artisan migrate
バリデーションの設定
app/Http/Controllers/UploadersController.php
のconfirm
の中に書いていきます。
public function confirm(Request $request)
{
$request->validate([
'name' => 'required',
'image' => 'required|image',
]);
return view('uploads.confirm');
}
バリデーションの意味は、以下の通りです。
※参考:バリデーション 6.x Laravel
- required:入力データが存在しており、かつ空でないことをバリデート
- image:ファイルが画像(jpg、png、bmp、gif、svg、webp)であることをバリデート
index.blade.php
にエラーを表示させます。
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード</h1>
<div class="container mb-5">
{{ Form::open(['route' => 'confirm', 'method' => 'POST', 'enctype' => 'multipart/form-data']) }}
@csrf
<div class="form-group row">
<p class="col-sm-4 col-form-label">お名前</p>
<div class="col-sm-8">
{{ Form::text('name', null, ['class' => 'form-control']) }}
</div>
</div>
{{-- エラー表示 --}}
@if ($errors->first('name'))
<p class="alert alert-danger">{{ $errors->first('name') }}</p>
@endif
<div class="form-group row">
<p class="col-sm-4 col-form-label">画像</p>
<div class="col-sm-8">
{{ Form::file('image') }}
</div>
</div>
{{-- エラー表示 --}}
@if ($errors->first('image'))
<p class="alert alert-danger">{{ $errors->first('image') }}</p>
@endif
<div class="text-center">
{{ Form::submit('確認画面へ', ['class' => 'btn btn-primary']) }}
</div>
{{ Form::close() }}
</div>
@endsection
このままだと英語で表示されるので、日本語化していきます。
resources/lang
のenフォルダと同階層にjaフォルダを作成し、validation.php
をコピペ。
修正した箇所は以下の通りです。
'required' => ':attributeは必須項目です。',
'image' => ':attributeは画像(jpg、png、bmp、gif、svg、webp)をアップロードしてください。',
'attributes' => [
'name' => 'お名前',
'image' => '画像',
],
トップページで入力した情報を、確認画面で表示する
app/Http/Controllers/UploadersController.php
のconfirm
に追記します。
public function confirm(Request $request)
{
$request->validate([
'name' => 'required',
'image' => 'required|image',
]);
// お名前を取得
$name = $request->name;
// 拡張子つきでファイル名を取得
$imageName = $request->file('image')->getClientOriginalName();
// 拡張子のみ
$extension = $request->file('image')->getClientOriginalExtension();
// 新しいファイル名を生成(形式:元のファイル名_ランダムの英数字.拡張子)
$newImageName = pathinfo($imageName, PATHINFO_FILENAME) . "_" . uniqid() . "." . $extension;
$request->file('image')->move(public_path() . "/img/tmp", $newImageName);
$image = "/img/tmp/" . $newImageName;
return view('uploads.confirm', [
'name' => $name,
'image' => $image,
'newImageName' => $newImageName,
]);
}
やっていることを簡単に説明します。
名前の取得はそのままなので、いいと思います。
画像の取得に関しては、あらかじめpublic/img
フォルダと、その配下にtmp
フォルダを作った上で、以下の手順で行っています。
- アップロードされた画像のファイル名を取得(拡張子付き)
- 拡張子だけの変数(jpg、pngなど)を定義しておく
- 取得したファイル名から新しいファイル名を生成
(形式:元のファイル名_ランダムの英数字.拡張子) - tmpフォルダに上記の画像ファイルを移動する
アップロードしたファイルの情報を取得するために、以下のメソッドを使っています。
- getClientOriginalName:ファイル名を取得
- getClientOriginalExtension:ファイルの拡張子を取得
そして、confirm.blade.php
で表示します。
また、DBに挿入するために、hidden
でインプット内容を保持しておきます。
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード - 確認画面</h1>
<div class="container mb-5">
{{ Form::open(['route' => 'complete', 'method' => 'POST']) }}
@csrf
<div class="form-group row">
<p class="col-sm-4 col-form-label">お名前</p>
<div class="col-sm-8">
{{ $name }}
</div>
<input type="hidden" name="name" value="{{ $name }}">
</div>
<div class="form-group row">
<p class="col-sm-4 col-form-label">画像</p>
<div class="col-sm-8">
<img src="{{ $image }}" alt="">
</div>
<input type="hidden" name="image" value="{{ $newImageName }}">
</div>
<div class="text-center">
{{ Form::submit('登録', ['class' => 'btn btn-primary']) }}
</div>
{{ Form::close() }}
</div>
@endsection
これで名前と画像が表示されました。
DBにデータ保存
app/Http/Controllers/UploadersController.php
のcomplete
に、以下の内容を書いていきます。
- DBにデータを保存
public/img
の配下に名前のidと同じフォルダを生成- tmpフォルダから上記のフォルダへ移動
- tmpフォルダを空にする
このようになりました。
public function complete(Request $request)
{
$uploader = new Uploader();
$uploader->name = $request->name;
$uploader->image = $request->image;
$uploader->save();
// レコードを挿入したときのIDを取得
$lastInsertedId = $uploader->id;
// ディレクトリを作成
if (!file_exists(public_path() . "/img/" . $lastInsertedId)) {
mkdir(public_path() . "/img/" . $lastInsertedId, 0777);
}
// 一時保存から本番の格納場所へ移動
rename(public_path() . "/img/tmp/" . $request->image, public_path() . "/img/" . $lastInsertedId . "/" . $request->image);
// 一時保存の画像を削除
\File::cleanDirectory(public_path() . "/img/tmp");
return view('uploads.complete');
}
トップページにアップロードの一覧を表示する
DBに保存されている名前と画像の一覧を表示します。
app/Http/Controllers/UploadersController.php
のindex
に書いていきます。
public function index()
{
$uploadedImages = Uploader::orderBy('created_at', 'desc')->paginate(5);
return view('uploads.index', [
'uploadedImages' => $uploadedImages,
]);
}
登録されているものを、降順(登録した日付の新しいものから)で表示します。
さらに、ページネーションを置き、5個ずつ表示されるようになっています。
index.blade.php
は以下のようになりました。
@extends('layout')
@section('content')
<h1 class="text-center mt-2 mb-5">画像アップロード</h1>
<div class="container mb-5">
{{ Form::open(['route' => 'confirm', 'method' => 'POST', 'enctype' => 'multipart/form-data']) }}
@csrf
<div class="form-group row">
<p class="col-sm-4 col-form-label">お名前</p>
<div class="col-sm-8">
{{ Form::text('name', null, ['class' => 'form-control']) }}
</div>
</div>
@if ($errors->first('name'))
<p class="alert alert-danger">{{ $errors->first('name') }}</p>
@endif
<div class="form-group row">
<p class="col-sm-4 col-form-label">画像</p>
<div class="col-sm-8">
{{ Form::file('image') }}
</div>
</div>
@if ($errors->first('image'))
<p class="alert alert-danger">{{ $errors->first('image') }}</p>
@endif
<div class="text-center">
{{ Form::submit('確認画面へ', ['class' => 'btn btn-primary']) }}
</div>
{{ Form::close() }}
</div>
{{-- 追記ここから --}}
<div class="container">
<table class="table table-striped">
<thead>
<tr>
<th scope="col" style="width: 30%">お名前</th>
<th scope="col">画像</th>
</tr>
</thead>
<tbody>
@foreach ($uploadedImages as $uploadedImage)
<tr>
<td>{{ $uploadedImage->name }}</td>
<td><img src="{{ asset('img/' . $uploadedImage->id . "/" . $uploadedImage->image) }}"></td>
</tr>
@endforeach
</table>
</div>
{{ $uploadedImages->links() }}
{{-- 追記ここまで --}}
@endsection
以上で、完成です。お疲れさまでした\(^o^)/
【まとめ】画像ファイルのアップロード方法【確認/完了画面つき】
画像アップロードはつまずくポイントだということで、練習がてら作成してみました。
コードをすべて見たい!という方は、こちら(GitHub)からどうぞ。
以上です。最後まで見てくださり、ありがとうございました。
» Laravelで作成したアプリやアウトプットの一覧はこちら
最後に宣伝
ココナラで、コーディングの相談を受け付けています。
- CSSが上手く作れない
- JavaScriptが思ったように動かない
- ブログのデザインを修正したい
- 勉強中でわからないところがあるから教えてほしい
このようなお悩みを解決していますので、「こんなの解決できる?」ということがあったら、ぜひ質問だけでも以下のリンクよりどうぞ。
コメント
助かります