라라벨 공부
개요
우리가 만들 사이트는 Dropzone.js를 이용해 Drag & Drop으로 이미지를 저장할 수 있는 일종의 미디어 라이브러리를 만들고자 합니다, 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부 포스트 주소입니다.
'PHP > Laravel' 카테고리의 다른 글
Laravel9 라라벨 csv 읽어서 저장하기(CSV seeding) (0) | 2022.09.07 |
---|---|
Laravel9 이미지 업로드 (with Dropzone) 2부 : Index & Destroy & Ajax (0) | 2022.08.30 |
Laravel9 라라벨 난수 액셀 다운로드 (Laravel-Excel) (2) | 2022.08.22 |
Laravel9 라라벨 액셀 다운로드 (Laravel-Excel) (0) | 2022.08.19 |
Laravel9 라라벨 액셀 다운로드 (Fast-Excel) (0) | 2022.08.19 |