【Laravel6】画像ファイルのアップロード方法【確認/完了画面付き】

CODE

この記事は、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」を使うので、インストールします。

※参考:Laravel Collective

composer require laravelcollective/html

レイアウトを共通化する

headタグなど、すべてのファイルに共通している部分を、共通化していきます。

resources/viewslayout.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

@extendslayout.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.phpconfirmの中に書いていきます。

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.phpconfirmに追記します。

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.phpcompleteに、以下の内容を書いていきます。

  • 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.phpindexに書いていきます。

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が思ったように動かない
  • ブログのデザインを修正したい
  • 勉強中でわからないところがあるから教えてほしい

このようなお悩みを解決していますので、「こんなの解決できる?」ということがあったら、ぜひ質問だけでも以下のリンクよりどうぞ。

HTML / CSS / JSのお悩みを解決します コーディングでお困りの方はお気軽にお問い合わせください! | Webサイト制作に関する相談 | ココナラ
HTML / CSS / JavaScript(jQuery)のお悩み相談、ご質問を受け付けます。「CSSが上手く作れない」「JavaScriptが思ったように...

コメント

タイトルとURLをコピーしました