PHP/Laravel

Laravel9 이미지 업로드 (with Dropzone) 1부 : Setting & Create & Image Upload

DSeung 2022. 8. 29. 17:19

라라벨 공부

바로가기

 



개요

우리가 만들 사이트는 Dropzone.js를 이용해 Drag & Drop으로 이미지를 저장할 수 있는 일종의 미디어 라이브러리를 만들고자 합니다, Dropzone.js를 쓰지 않는 이미지 업로드를 찾는 분들이라 하더라도 충분히 도움이 될 것입니다.

Dropzone.js

 

이번 포스트에서는 프로젝트 세팅과 이미지 업로드 기능을 포함하는 create 기능을 만들 것입니다.

 

참고로 라라벨9 버전과 PHP 8.1.8를 사용합니다.

 

1. Project Setting

 

dropzone이라는 이름으로 프로젝트를 만들어줍시다.

composer create-project --prefer-dist laravel/laravel dropzone "9.*"
cd dropzone
composer install
php artisan key:generate

--prefer-dist : Forces installation from package dist (default behavior) = 기본 설치

"9.*" : 다운로드할 라라벨 버전입니다.

 

 

이제 mysql에 접속하여 DB를 생성해줍시다.

create database dropzone;

 

그리고 프로젝트의 .env를 수정해 생성한 DB와 연결해줍시다.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=dropzone
DB_USERNAME=root
DB_PASSWORD=secret

 

그리고 Image 모델과 마이그레이션을 만들어줍시다

php artisan make:model Image -m

create_image_table.php의 코드는 아래와 같이 수정해줍시다.

...
    
    public function up()
    {
    	Schema::create('images', function (Blueprint $table) {
            $table->id(); // index
            $table->string('origin_name'); // 파일의 원래 이름
            $table->text('path'); // 파일 저장 경로
            $table->timestamps();
        });
    }

...

Image 모델에서도 데이터 저장 화이트 리스트에 값을 추가해줍시다.

이를 추가하지 않으면 데이터 저장을 할 수가 없습니다.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
    use HasFactory;

    protected $fillable = [
        'origin_name', 'path'
    ];
}

 

2. Create & Upload

 

ImageController를 만들어 줍시다.

php artisan make:controller ImageController

 

라라벨의 컨트롤러에서 모델을 사용할 경우 IOC 컨테이너, 의존성 주입 컨테이너 방식을 사용하는 것을 권합니다.

아래와 같이 Controller를 수정해줍시다.

<?php

namespace App\Http\Controllers;

use App\Models\Image;

class ImageController extends Controller
{
    // ioc
    private $image;

    public function __construct(Image $image)
    {
        // ioc
        $this->image = $image;
    }

    public function create()
    {
        return view('image/create');
    }
}

 

그리고 resources/views/image/create.blade.php 경로로 view 파일을 만들어줍시다.

저는 편의성을 위해 Jquery와 Bootstrap5도 추가로 사용했습니다.

<!DOCTYPE html>
<html>
<head>
    <title>dropzone</title>
    <meta name="_token" content="{{csrf_token()}}"/>
    {{-- dropzone.js --}}
    <script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
    {{-- Jquery CDN --}}
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    {{-- dropzone.css --}}
    <link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css"/>
    {{-- bootstrap5 css --}}
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">

    <h4 class="pt-4">Laravel DropZone</h4>
    <hr class="pd-4"/>
    <form method="post" action="#" enctype="multipart/form-data" class="dropzone" id="dropzone">
        @csrf
    </form>
    <button type="submit" id="submit-all" class="btn btn-primary btn-xs">Upload the file</button>

    <script type="text/javascript">
        Dropzone.options.dropzone =
            {
                autoProcessQueue: false, // 자동 업로드 방지
                uploadMultiple: true, // 여러개 업로드 허용
                maxFilesize: 50, // 최대 파일 사이즈 (MB)
                parallelUploads: 5, // 한번에 업로드 가능 한 수
                maxFiles: 5, //올릴 수 있는 파일의 개수
                acceptedFiles: ".jpeg,.jpg,.png,.gif", // 허용 확장자
                timeout: 50000, // 최대 시간
                renameFile: function (file) {
                    // 파일 업로드시 이름 변경
                    let dt = new Date();
                    let time = dt.getTime();
                    return time + "_" + file.name;
                },
                init: function () {
                    let myDropzone = this;

                    // submit-all 버튼을 클릭 해야만 파일 업로드
                    $("#submit-all").click(function (e) {
                        e.preventDefault();
                        // 큐에 파일 적재 => 업로드 실행
                        myDropzone.processQueue();
                    });

                    /*  이미지를 보낼 때 추가 input 추가
                        여기서는 원래 이름을 기억하기 위해 origin_name 추가*/
                    myDropzone.on("sending", function(file, xhr, formData){
                        formData.append("origin_name[]", file.name);
                    })
                },
                success: function (file, response) {
                    console.log("success");
                },
                error: function (file, response) {
                    console.log("error");
                    return false;
                }
            };
    </script>
</body>
</html>

좀 길어 보이지만 실제로 코드 양은 정말 얼마 안 됩니다.

이제 web.php에 라우트를 추가해주시면 됩니다.

<?php

use App\Http\Controllers\ImageController;
use Illuminate\Support\Facades\Route;

Route::get('/create', [ImageController::class, 'create'])->name('image.create');

이제 http://dropzone.test/create 각자 등록해준 사이트에 create로 들어가시면 아래와 같이 이미지 drag & drop이 가능한 것을 확인할 수 있습니다.

라라벨 이미지 업로드

하지만 저장하는 기능을 만들지 않아 실제로 업로드와 데이터 저장이 되지 않습니다.

 

 

3. Store & Upload

이제 데이터 저장 및 이미지 업로드 작업을 해줍시다.

web.php에 다음 코드를 추가해줍시다.

Route::post('/store', [ImageController::class, 'store'])->name('image.store');

 

그리고 app/Http/Controllers/ImageController.php도 수정을 해줍시다.

<?php

namespace App\Http\Controllers;

use App\Models\Image;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class ImageController extends Controller
{
    ...

    public function store(Request $request)
    {
        // 업로드한 파일
        $files = $request->file('file');
        // dropzone sending 이벤트에서 추가하 원래 파일의 이름들
        $originNames = $request->input('origin_name');

        foreach ($files as $key => $file) {
            /*
             * Storage::putFileAs은 storage/app을 의미합니다, 그렇기에 결과적으로 저장되는 경로는 아래와 같습니다.
             * storage/app/uploads/22-08-29/1661757373818_git cat.png
             * $file->getClientOriginalName은 dropzone에서 renameFile로 바꾼 이름입니다.
             */
            $path = Storage::putFileAs(
                "uploads/" . date('y-m-d'), $file, $file->getClientOriginalName()
            );
            $this->image->create([
                'origin_name' => $originNames[$key],
                'path' => $path
            ]);
        }
        return response()->json(['success' => true]);
    }
}

 

라라벨에서는 storage에 파일을 저장하는 방식을 권장합니다.

하지만 여기서 이상한 점이 하나 발생합니다.
보통 이미지를 불러오려면 public 폴더 안에 있는 파일을 가져오는 것이죠.

 

그렇기에 우리는 config/filesystems.php 파일로 이동 후 links를 수정해줍시다

...

	'links' => [
        // 아래 처럼 추가 후, php artisan storage:link 하면 심볼릭 링크 생성
        public_path('uploads') => storage_path('app/uploads'),
    ],
];

이제 php artisan storage:link  명령어를 사용하면 자동으로 symbolic link가 생성되면서 public 폴더에서 업로드한 파일을 사용이 가능해집니다.

 

하지만 여기서 당신이 window + Homestead 사용자라면 아마 권한 에러가 발생할 것입니다.

 

이때 제가 주로 사용하는 방법을 알려드리겠습니다.

vagrant ssh로 처음 접속하면 경로는 vagrant 사용자 폴더이실 것입니다.

여기서 vi .bashrc를 하시 후 가장 마지막 라인에 아래 코드를 추가합니다. (자신의 경로를 입력해주세요)

...
#custom
sudo mount --bind /home/vagrant/code/blog/dropzone/storage/app/uploads /home/vagrant/code/blog/dropzone/public/uploads

이러면 vagrant ssh 접속 시마다 자동으로 폴더를 마운트 해줘서 심볼릭 링크와 같은 결과를 얻을 수 있습니다.

 

symbolic link가 완료되었다면 다음 회차인 Index에서 이미지를 볼 수 있을 것입니다.

이제 다시 create.blade.php로 돌아와서 아래와 같이 수정해줍시다.

	...

    <h4 class="pt-4">Laravel DropZone</h4>
    <hr class="pd-4"/>
    {{-- 최대 업로드 양을 넘었을 경우 경고를 띄어줍시다. --}}
    <div class="alert alert-danger" role="alert" style="display: none">
        이미지는 한번에 5개까지 업로드가 가능합니다.
    </div>
    <form method="post" action="{{route('image.store')}}" enctype="multipart/form-data" class="dropzone" id="dropzone">
        @csrf
    </form>
    {{-- 프로그래스 바를 만들어줍시다. --}}
    <div class="progress mt-3 mb-3">
        <div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
    </div>
    <button type="submit" id="submit-all" class="btn btn-primary btn-xs">Upload the file</button>

    <script type="text/javascript">
        Dropzone.options.dropzone =
            {
                ...
                renameFile: function (file) {
                    // 파일 업로드시 이름 변경
                    let dt = new Date();
                    let time = dt.getTime();
                    return time + "_" + file.name;
                },
                init: function () {
                    let myDropzone = this;

                    // submit-all 버튼을 클릭 해야만 파일 업로드
                    $("#submit-all").click(function (e) {
                        e.preventDefault();
                        // 큐에 파일 적재 => 업로드 실행
                        myDropzone.processQueue();
                    });

                    // maxFiles에 도달했을 경우 경고창 띄우기
                    myDropzone.on("maxfilesexceeded", function (file) {
                        myDropzone.removeFile(file);
                        $(".alert").show();
                    });

                    // 업로드시 프로그래스 바 애니메이션 추가
                    myDropzone.on("totaluploadprogress", function (progress) {
                        $(".progress-bar").width(progress + '%');
                    });

                    // 원래 이름도 form에 추가
                    myDropzone.on("sending", function(file, xhr, formData){
                        formData.append("origin_name[]", file.name);
                    })
                },
                success: function (file, response) {
                    console.log("success");
                    console.log(response);
                },
                error: function (file, response) {
                    return false;
                }
            };
    </script>
</body>
</html>

이제 아래와 같은 결과를 얻을 수 있을 것입니다.

라라벨 이미지 업로드

아래와 같이 DB에도 알맞게 저장되었네요

이미지도 원하는 경로에 저장되었습니다.

 


2부 포스트 주소입니다.

바로가기

반응형