Pendahuluan
Dalam sebuah aplikasi dengan data inputan yang banyak, fitur bulk import merupakan bagian yang penting untuk mempercepat proses peng-inputan tersebut tanpa harus bolak-balik ke form input-an yang telah disediakan, user hanya perlu membuat data dengan format yang telah ditentukan, maka sekali upload semua data akan tersimpan ke dalam database.
Fitur seperti ini kerap kali kita temui dalam beberapa e-commerce, sebut saja e-commerce yang memiliki karakteristik warna orange telah menyediakan fitur upload produk secara massal, sehingga user hanya perlu menyusunnya kedalam sebuah file .xlsx.
Baca Juga: Upload Resize Image di Laravel 5.7
Import Data Excel
Secara sederhana, data dari file excel yang telah di-upload oleh user di-convert menjadi data array untuk kemudian dikelola sesuai yang diinginkan, dalam hal ini menyimpannya kedalam database. Kita akan memulainya dengan Laravel fresh install, kali ini menggunakan versi terbaru sejak artikel ini dibuat, yakni versi 5.7.
composer create-project --prefer-dist laravel/laravel excel-import-laravel
Kemudian install package dari Laravel excel 3.1 dengan command:
composer require maatwebsite/excel
Note: Pastikan kamu menggunakan Laravel 5.5 atau yang terbaru, dan juga telah mengaktifkan php extensions: php_zip, php_xml, php_gd2.
Adapun case yang akan diangkat adalah sebuah form sederhana untuk meng-import data produk kedalam table products
, so, buat table products terlebih dahulu dengan command:
php artisan make:model Product -m
Buka file migrations dari products
pada folder database/migrations
kemudian modifikasi menjadi
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('slug');
$table->text('description')->nullable();
$table->integer('price');
$table->integer('stock');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('products');
}
Kemudian modifikasi model app/Product.php
menjadi:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $guarded = []; //TAMBAHKAN LINE INI
}
Buat controller untuk meng-handle form dengan command:
php artisan make:controller ProductController
Kita akan memanfaatkan file welcome.blade.php
untuk membuat form upload file dengan format excel nantinya. Buka file resources/views/welcome.blade.php
kemudian modifikasi menjadi:
<!DOCTYPE html>
<html lang="en">
<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>Bulk Import Laravel Excel</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row" style="padding-top: 30px">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<form action="{{ url('/') }}" method="post" enctype="multipart/form-data">
@csrf
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="alert alert-success">
{{ session('error') }}
</div>
@endif
<div class="form-group">
<label for="">File (.xls, .xlsx)</label>
<input type="file" class="form-control" name="file">
<p class="text-danger">{{ $errors->first('file') }}</p>
</div>
<div class="form-group">
<button class="btn btn-primary btn-sm">Upload</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6"></div>
</div>
</div>
</body>
</html>
Saatnya untuk meng-handle file yang di-upload user pada ProductController.php, modifikasi file tersebut dengan menambahkan code berikut:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Imports\ProductsImport;
use Excel;
class ProductController extends Controller
{
public function storeData(Request $request)
{
//VALIDASI
$this->validate($request, [
'file' => 'required|mimes:xls,xlsx'
]);
if ($request->hasFile('file')) {
$file = $request->file('file'); //GET FILE
Excel::import(new ProductsImport, $file); //IMPORT FILE
return redirect()->back()->with(['success' => 'Upload success']);
}
return redirect()->back()->with(['error' => 'Please choose file before']);
}
}
Terdapat class ProductsImport
yang bertugas untuk mengolah data yang ada didalam file excel, buat class tersebut dengan command:
php artisan make:import ProductsImport --model=Product
Artisan command diatas akan meng-generate sebuah file dengan nama ProductsImport.php
dan terletak di dalam folder app/imports
. Buka file tersebut, kemudian tambahkan code berikut:
<?php
namespace App\Imports;
use App\Product;
use Maatwebsite\Excel\Concerns\ToModel;
class ProductsImport implements ToModel
{
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Product([
'title' => $row[0],
'slug' => str_slug($row[0]),
'description' => $row[1],
'price' => $row[2],
'stock' => $row[3]
]);
}
}
Penjelasan: Data yang berada didalam file excel akan di-reformat menjadi array, sehingga untuk tiap barisnya diwakilkan dengan index array yang dimulai dari 0. Pastikan kamu memulai memasukkan data dari cell A1 dan seterusnya pada saat menyusun data excelnya.
Terakhir buka file routes/web.php
kemudian tambahkan route berikut:
Route::post('/', 'ProductController@storeData');
Berikut adalah contoh dalam menyusun data kedalam file Excel.
Penjelasan: Cell A adalah kolom untuk title
, Cell B untuk description
, Cell C untuk price
, dan Cell D untuk stock
. Sedangkan untuk slug
kita ambil dari title
saja untuk value-nya.
Data yang masuk sesuai dengan data yang ada didalam file excel.
Import With Heading
Data yang disusun kedalam file excel juga dapat diberikan heading yang juga sekaligus bertugas sebagai index key dari array nantinya. Sebab akan membingungkan apabila tidak terdapat kolom untuk menjelaskan tujuan dari kolom tersebut. Untuk skala kolom yang ada didalam case ini mungkin saja masih bisa di-ingat, akan tetapi jika sudah lebih banyak tentunya akan sangat merepotkan karena harus mengintip database terlebih dahulu. Nah bagaimana jadinya, jika yang membuat susunan data tersebut bukan programmer, melainkan hanya sebatas user saja yang tidak memiliki akses ke database?
Maka disinilah perlunya kita memberikan heading untuk susunan data yang akan diterima. Buka file app/imports/ProductsImport.php
kemudian modifikasi menjadi:
<?php
namespace App\Imports;
use App\Product;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow; //TAMBAHKAN CODE INI
class ProductsImport implements ToModel, WithHeadingRow // USE CLASS YANG DIIMPORT
{
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
//MODIFIKASI ARRAY NYA DENGAN MENAMBAHKAN KEY
return new Product([
'title' => $row['title'],
'slug' => str_slug($row['title']),
'description' => $row['description'],
'price' => $row['price'],
'stock' => $row['stock']
]);
}
}
Sedangkan pada saat menyusun datanya pada file Excel, kamu dapat membuatnya seperti berikut
Dapat kamu lihat, pada baris pertama berisi heading yang nantinya akan di-convert secara otomatis menggunakan helper str_slug
yang kemudian menjadi key dari array.
Baca Juga Tutorial Vuex #3: Module Helpers
Membuat Queue Importing
Queue bertujuan untuk memindahkan sebuah proses ke sisi server, sehingga user tidak perlu menunggu proses tersebut selesai dan tentu saja akan meminimalisir kendala yang terjadi selama proses tersebut berlangsung, misalnya saja koneksi internet mati dan lain sebagainya.
Untuk jumlah data yang masih sedikit, mungkin masih berlangsung dalam hitungan kurang detik saja. Namun apa jadinya jika sudah dalam jumlah yang banyak? Dan tentu saja untuk sebuah fitur Bulk Import akan meng-handle data yang tergolong banyak untuk mempercepat proses inputan user. So, Queue sangat dibutuhkan disini.
Generate table untuk menyimpan list jobs dengan command:
php artisan queue:table
php artisan migrate
Kemudian buat class untuk meng-handle jobs dengan command:
php artisan make:job ImportJob
Buka file app/Jobs/ImportJob.php
kemudian modifikasi menjadi:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Excel; //IMPORT CLASS EXCEL
use App\Imports\ProductsImport; //IMPORT CLASS PRODUCTSIMPORT
class ImportJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $file;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($file)
{
$this->file = $file; //MENERIMA PARAMETER YANG DIKIRIM
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Excel::import(new ProductsImport, 'public/' . $this->file); //MENJALANKAN PROSES IMPORT
unlink(storage_path('app/public/' . $this->file)); //MENGHAPUS FILE EXCEL YANG TELAH DI-UPLOAD
}
}
Kemudian pada controller ProductController.php
, modifikasi method storeData()
menjadi:
public function storeData(Request $request)
{
$this->validate($request, [
'file' => 'required|mimes:xls,xlsx'
]);
if ($request->hasFile('file')) {
//UPLOAD FILE
$file = $request->file('file');
$filename = time() . '.' . $file->getClientOriginalExtension();
$file->storeAs(
'public', $filename
);
//MEMBUAT JOBS DENGAN MENGIRIMKAN PARAMETER FILENAME
ImportJob::dispatch($filename);
return redirect()->back()->with(['success' => 'Upload success']);
}
return redirect()->back()->with(['error' => 'Please choose file before']);
}
Jangan lupa untuk meng-import class ImportJob
:
use App\Jobs\ImportJob;
Terakhir buka file app/Imports/ProductsImport.php
, kemudian modifiksai menjadi:
<?php
namespace App\Imports;
use App\Product;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Illuminate\Contracts\Queue\ShouldQueue; //IMPORT SHOUDLQUEUE
use Maatwebsite\Excel\Concerns\WithChunkReading; //IMPORT CHUNK READING
class ProductsImport implements ToModel, WithHeadingRow, WithChunkReading, ShouldQueue
{
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Product([
'title' => $row['title'],
'slug' => str_slug($row['title']),
'description' => $row['description'],
'price' => $row['price'],
'stock' => $row['stock']
]);
}
//LIMIT CHUNKSIZE
public function chunkSize(): int
{
return 1000; //ANGKA TERSEBUT PERTANDA JUMLAH BARIS YANG AKAN DIEKSEKUSI
}
}
Penjelasan: ChunkReading
bertugas untuk meng-handle jumlah data yang besar, karena library akan mencoba me-load seluruh data yang ada sehingga akan menjadi beban bagi memory server. So, ChunkReading
akan membuat sekat dan me-loadnya secara berkala.
Terakhir kamu bisa menjalankan command php artisan queue:work
untuk mengeksekusi job yang telah dibuat. Ohya jangan lupa untuk mengubah file .env
, perhatian bagian DATABASE
ganti menjadi:
QUEUE_CONNECTION=database
Atau kamu bisa menggunakan supervisor agar fungsi queue:work
berjalan secara otomatis.
Kesimpulan
Menyelesaikan permasalah penginputan data dengan skala besar dapat diselesaikan dengan fitur bulk import disertai queue sehingga seluruh prosesnya dapat dikerjakan oleh server, sehingga user tidak perlu menghabiskan banyak waktu untuk menyelesaikan persoalan tersebut.
Ohya untuk dokumentasi code dapat kamu lihat di Github.
Comments