Membuat Fitur Bulk Import Laravel Excel 3.1

Membuat Fitur Bulk Import Laravel Excel 3.1

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.

import laravel 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.

import laravel excel

Data yang masuk sesuai dengan data yang ada didalam file excel.

import laravel excel phpmyadmin

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

import laravel excel with heading

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.

Category:
Share:

Comments