Upload File Menggunakan Filesystem Laravel


Laravel sudah menyediakan fitur yang bernama filesystem. Fitur ini berfungsi untuk mengatur berkas yang disimpan (atau akan disimpan) pada server, baik di server lokal, maupun di server terpisah seperti AWS, Dropbox, dan lain sebagainya. Berkas yang akan disimpan pun bisa datang dari berbagai sumber, bisa berupa salinan dari server lain, atau bisa juga unggah (upload) melalui sebuah formulir (form) dari pengguna.

Pada tulisan ini, akan dibahas bagaimana cara mengunggah sebuah berkas (file) dari sebuah formulir ke server aplikasi dan disimpan ke storage lokal. Pembuatan aplikasi unggah berkas ini nantinya akan melibatkan beberapa fitur lain, seperti migration, validation, serta filesystem itu sendiri. Untungnya, fitur unggah di Laravel sudah terintegrasi dengan filesystem, sehingga proses pembuatan unggah berkas jauh lebih mudah dan singkat.

Baca terlebih dahulu Tiga Cara Mengunduh & Menginstal Laravel.

Menyiapkan Pangkalan Data

Informasi berkas yang diunggah ke server akan disimpan ke dalam pangkalan data (database). Data yang disimpan berupa judul berkas yang bisa diisi oleh pengguna atau diambil dari nama asli berkas. Dan tak lupa, kita juga menyimpan path berkas yang sudah berhasil diunggah ke storage lokal. Perlu ditekankan, yang disimpan hanya path dari berkas tersebut, bukan berkas fisik yang disimpan dalam bentuk data binary.

$ mysql > show columns from files;

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| title      | varchar(100)     | NO   |     | NULL    |                |
| filename   | varchar(100)     | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

Untuk membuat tabel dengan struktur seperti di atas, kita dapat memanfaatkan fitur migration di Laravel. Untuk pembuatan lebih cepat, kalian cukup menjalankan perintah Artisan seperti potongan di bawah.

$ php artisan make:migration create_files_migration --create=files

Buka berkas yang berada di dalam direktori database/migrations dan mengandung kata “create_files_migration”. Modifikasi skrip tersebut untuk membuat kolom seperti struktur tabel di atas. Skrip lengkapnya dapat dilihat pada potongan di bawah.

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFilesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('files', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title', 100);
            $table->string('filename', 100);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('files');
    }
}

Migration sudah siap dijalankan. Untuk menjalankan migration dan membuat tabel, kalian cukup menjalankan perintah di bawah.

$ php artisan migrate

Pastikan konfigurasi pangkalan data dalam berkas .env sudah kalian atur menyesuaikan server  pangkalan data di mesin lokal.

Membuat Model

Tabel sudah terbentuk dari migration. Langkah selanjutnya menyiapkan model untuk tabel files. Pembuatan model juga bisa dipercepat dengan menggunakan perintah Artisan.

$ php artisan make:model File

Dari perintah di atas, maka Laravel akan membuat sebuah model dengan nama File.php dalam direktori app.

Karena penamaan tabel sudah sesuai dengan aturan yang diterapkan Laravel, maka kita tidak perlu mendefinisikan nama tabel dalam model. Yang perlu kita lakukan adalah mendefinisikan atribut menggunakan properti $fillabble sebagai refleksi kolom di tabel.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class File extends Model
{
    protected $fillable = [
        'title',
        'filename'
    ];
}

Baca juga Berkenalan dengan Magic Property (Properti) Eloquent.

Controller

Dalam controller, akan dibuat tiga buah method, yaitu index(), form(), dan upload(). Untuk pembuatan cepat, kita dapat menggunakan perintah Artisan berikut.

$ php artisan make:controller FileController

Dari perintah di atas, akan terbentuk sebuah berkas dengan nama FileController.php dalam direktori app/Http/Controllers.

Perhatikan potongan skrip di bawah, yang mana method index() berfungsi untuk mengambil data berkas dari pangkalan data, kemudian menampilkannya pada view. Sedangkan untuk method form() hanya berfungsi untuk menampilkan formulir unggah berkas. Formulir unggah ini akan dibuat pada bahasan selanjutnya.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

// impor model file
use App\File;
use Illuminate\Http\RedirectResponse;

class FileController extends Controller
{
    public function index(): View
    {
        $files = File::orderBy('created_at', 'DESC')
            ->paginate(30);

        return view('file.index', compact('files'));
    }

    public function form(): View
    {
        return view('file.form');
    }

    public function upload(Request $request): RedirectResponse
    {
         
    }
}

 

Khusus untuk method upload() sementara dikosongkan, dan akan dibahas lebih lanjut pada poin khusus di bawah.

Daftarkan controller tersebut pada berkas routes/web.php agar dapat diakses melalui HTTP secara publik.

Route::get('file/upload', 'FileController@form')->name('file.form');
Route::get('file/index', 'FileController@index')->name('file.index');
Route::post('file/upload', 'FileController@upload')->name('file.upload');

Pada route, saya menambahkan method name($name). Nama route ini akan saya gunakan pada beberapa skrip alih-alih menggunakan URL secara langsung.

View

Sebelum masuk lebih jauh ke proses unggah berkas, ada baiknya kita menyiapkan formulir dan indeks berkas terlebih dahulu.

Pembuatan formulir unggah berkas (form upload) akan menggunakan layout yang sudah disediakan oleh Laravel. Sebelum menggunakan layout tersebut, terlebih dahulu kita harus mengaktifkan fitur auth bawaan Laravel.

$ php artisan make:auth

Dari perintah di atas, akan kita dapati beberapa berkas baru dalam direktori resources/views. Dan paling penting adalah berkas app.blade.php yang terletak dalam direktori resources/views/layouts. Berkas ini nantinya akan digunakan sebagai layout utama formulir unggah dan indeks berkas.

Template Index

Template ini berfungsi untuk menampilkan daftar berkas yang sudah diunggah. Berkas template-nya sendiri dimuat oleh method index() dalam class FileController. Pada method tersebut di-assign sebuah variabel dengan nama $files. Variabel $filesini bertipe Eloquent collection yang berisi data dari tabel files.

Buat berkas baru dengan nama index.blade.php dalam direktori resources/views/file. Skrip lengkapnya dapat disalin dari media di bawah.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="panel panel-default">
                <div class="panel-heading">Upload New File</div>

                <div class="panel-body">

                    @if(session('success'))
                        <div class="alert alert-success">
                            {{ session('success') }}
                        </div>
                    @endif

                    <p>
                        <a href="{{ route('file.form') }}" class="btn btn-primary">Upload File</a>
                    </p>

                    <div class="table-responsive">
                        <table class="table table-bordered">
                            <thead>
                                <tr>
                                    <th>Title</th>
                                    <th>Path</th>
                                    <th>URL</th>
                                    <th>Created</th>
                                </tr>
                            </thead>
                            <tbody>
                                @foreach($files as $file)
                                    <tr>
                                        <td>{{ $file->title }}</td>
                                        <td>{{ $file->filename }}</td>
                                        <td>
                                            <a href="{{ Storage::url($file->filename) }}">
                                                View
                                            </a>
                                        </td>
                                        <td>{{ $file->created_at->diffForHumans() }}</td>
                                    </tr>
                                @endforeach
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Hasil akhir dari template di atas akan membentuk tampilan seperti gambar di bawah.

Daftar berkas yang berhasil diunggah

Template Formulir Unggah (Form Upload)

Template selanjutnya berisi formulir yang memungkinkan pengguna untuk mengunggah sebuah berkas. Bentuknya kurang lebih seperti pada gambar berikut.

Buat berkas baru dengan nama form.blade.php pada direktori resrouces/views/file. Tambahkan tag HTML pada berkas tersebut berdasarkan skrip di bawah. Perhatikan juga beberapa baris skrip yang saya tandai.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Upload New File</div>

                <div class="panel-body">

                    @if(session('success'))
                        <div class="alert alert-success">
                            {{ session('success') }}
                        </div>
                    @endif

                    <form action="{{ route('file.upload') }}" method="post" enctype="multipart/form-data">
                        {{ csrf_field() }}
                        {{ method_field('post') }}

                        <div class="form-group {{ !$errors->has('title') ?: 'has-error' }}">
                            <label>Title</label>
                            <input type="text" name="title" class="form-control">
                            <span class="help-block text-danger">{{ $errors->first('title') }}</span>
                        </div>

                        <div class="form-group {{ !$errors->has('file') ?: 'has-error' }}">
                            <label>File</label>
                            <input type="file" name="file">
                            <span class="help-block text-danger">{{ $errors->first('file') }}</span>
                        </div>

                        <div class="form-actions">
                            <button type="submit" class="btn btn-primary">Upload</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Pastikan untuk menambahkan atribut enctype="multipart/form-data" pada tag form afar memungkinkan form mengirim data dalam bentuk binary.

Tambahkan juga fungsi csrf_field() dalam tag form untuk meng-generate sebuah input tersembunyi dengan nama token. Input ini berfungsi sebagai identifier untuk menghindari CSRF. Sebagai alternatif, kalian juga dapat menuliskannya seperti berikut.

<input type="hidden" name="_token" value="{{ token() }}">

Di dalam form juga ditambahkan fungsi method_field($method). Penggunaan fungsi ini sebenarnya dapat diabaikan apabila action pada route tipenya adalah post. Fungsi method_field($method) berfungsi untuk menyimulasikan request method yang sampai saat ini belum didukung oleh HTML. Sebagai contoh singkat:

Route::put('file/update/{file}', 'FileController@update')->name('file.update');

Untuk formulirnya, dapat kita tulis seperti potongan berikut.

<form action="{{ route('file.update', $file->id) }}" method="post" enctype="multipart/form-data">
{{ csrf_field() }}
{{ method_field('put') }}

Proses Unggah Berkas

Formulir unggah sudah siap, kembali ke controller untuk proses unggah berkas. Sebelum proses unggah dijalankan, pastikan dahulu data yang dikirim melalui formulir sudah divalidasi. Contoh validasi pada tulisan ini menggunakan controller helper.

$this->validate($request, [
    'title' => 'nullable|max:100',
    'file' => 'required|file|max:2000', // max 2MB
]);

Pada contoh di atas, input title boleh dikosongkan oleh pengguna. Di aplikasi, nantinya dilakukan pengecekan, apabila title tidak diisi oleh pengguna, maka otomatis diisi dengan nama berkas asli.

Untuk penggunaan validasi yang lebih lanjut, kalian bisa membacanya pada tulisan berikut.

Seperti yang sudah disinggung di atas, filesystem sudah terintegrasi dengan request. Sehingga, untuk proses unggah berkasnya kita cukup menyambungkan method baru dengan nama store($path). Hal pertama yang dilakukan adalah menampung berkas temporer ke dalam variabel baru.

// tampung berkas yang sudah diunggah ke variabel baru
// 'file' merupakan nama input yang ada pada form
$uploadedFile = $request->file('file');        

// simpan berkas yang diunggah ke sub-direktori 'public/files'
// direktori 'files' otomatis akan dibuat jika belum ada
$path = $uploadedFile->store('public/files');

Jika kita debug variabel $path, maka akan menghasilkan nilai yang isinya kurang lebih sebagai berikut:

public/files/6Q0nhhM9r3cYRB8IbFWMQTvuyYzVzBVbcpF7eCda.jpeg

Nilai ini yang akan kita simpan ke dalam pangkalan data sebagai informasi letak berkas yang telah diunggah. Laravel juga otomatis mengubah nama berkasnya menjadi acak untuk menghindari konflik.

Perlu kalian ketahui, berkas yang sudah diunggah dan disimpan tadi terletak dalam direktori storage/app/public, jadi bukan dalam direktori public.

Langkah terakhir, menyimpan informasi berkas yang sudah diunggah ke dalam pangkalan data.

$file = File::create([
    'title' => $request->title ?? $uploadedFile->getClientOriginalName(),
    'filename' => $path
]);

Di bagian title, kita memeriksa apakah input tersebut bernilai null. Apabila nilainya null, maka akan mengambil nama dari berkas asli. Penggunaan simbol “??” merupakan fitur null coalescing operator yang ada di PHP.

Laravel menggunakan package UploadedFile dari Symfony, sehingga kita bisa menggunakan method yang ada pada API tersebut untuk mendapatkan berbagai informasi dari berkas yang telah diunggah. Sebagai contoh, lihat pada potongan skrip berikut:

// mendapatkan nama berkas asli
$request->file('file')->getClientOriginalName();

// mendapatkan ektensi berkas
$request->file('file')->getClientOriginalExtension();

// mendapatkan ukuran berkas
$request->file('file')->getClientSize();

Skrip lengkap dari potongan di atas untuk method upload() dapat dilihat pada media berikut.

public function upload(Request $request): RedirectResponse
{
    $this->validate($request, [
        'title' => 'nullable|max:100',
        'file' => 'required|file|max:2000'
    ]);

    $uploadedFile = $request->file('file');        

    $path = $uploadedFile->store('public/files');

    $file = File::create([
        'title' => $request->title ?? $uploadedFile->getClientOriginalName(),
        'filename' => $path
    ]);

    return redirect()
        ->back()
        ->withSuccess(sprintf('File %s has been uploaded.', $file->title));        
}

Membuat Symbolic Link

Demi keamanan, Laravel menyimpan berkas unggahan ke dalam direktori storage/app. Penyimpanan dalam direktori ini juga menghindari berkas terunggah ke CVS seperti Git. Secara bawaan, isi dari direktori storage akan otomatis diabaikan untuk di-commit.

Lantas, bagaimana caranya mengakses berkas dalam direktori storage untuk publik? Padahal, direktori yang dapat diakses melalui HTTP hanya berkas yang berada dalam direktori public.

In computing, a symbolic link (also symlink or soft link) is the nickname for any file that contains a reference to another file or directory in the form of an absolute or relative path and that affects pathname resolution.

Untuk hal ini, kita dapat membuat symbolic link dalam direktori public yang mengarah ke direktori storage/app/public. Tak perlu pusing untuk membuatnya, karena Laravel sudah menyediakan perintah pembuatannya melalui Artisan.

$ php artisan storage:link

Jika kalian buka direktori public, maka akan nampak sebuah direktori dengan nama storage. Direktori ini merupakan alias yang mengarah ke direktori storage/app/public.

Direct Link ke Berkas

Informasi path sudah didapatkan dan disimpan ke dalam pangkalan data ketika proses unggah berkas. Namun, path ini bukan path publik yang dapat diakses langsung melalui HTTP. Untuk mendapatkan path publik, kita dapat menggunakan method url($path) yang ada pada facade Storage.

Sebagai contoh, path yang disimpan dalam pangkalan data adalah sebagai berikut:

$path = 'public/files/UCEQ98OX61VUZHwT7iSRUAUwRbTyW49q2IZB0fBm.jpeg'

Maka, dengan menggunakan method Storage::url($path), akan terbentuk path publik seperti berikut.

$publicPath = \Storage::url($path);

// storage/files/UCEQ98OX61VUZHwT7iSRUAUwRbTyW49q2IZB0fBm.jpeg

Ketika sudah mendapatkan path publik, kalian bisa menggunakannya untuk berbagai keperluan, seperti menampilkan gambar yang telah diunggah oleh pengguna.

<img src="{{ Storage::url($file->path) }}" title="{{ $file->title }}">

Dapat juga dimanfaatkan untuk direct link unduh berkas.

Contoh Aplikasi dan Repositori

Agar kalian tak perlu membayang-bayangkan bentuk aplikasi jadi, saya sudah menyiapkan sebuah contohnya yang dapat kalian coba langsung melalui tautan berikut.

Contoh aplikasi

Contoh aplikasi

Aplikasi juga dapat kalian coba langsung pada mesin lokal dengan menyalinnya dari repositori berikut. Pastikan ikuti langkah instalasi yang tertera agar aplikasi dapat berjalan semestinya.

***

Menggunakan filesystem ketika mengatur berkas memberikan banyak keuntungan. Salah satunya pada contoh implementasi di atas, terintegrasi dengan request. Kelebihan lain misalnya, kalian dapat menentukan lokasi penyimpanan berkas, baik di lokal maupun di cloud. Perubahan lokasi penyimpanan tentu saja tidak memerlukan banyak perubahan skrip di aplikasi.

Ada banyak hal juga yang bisa kalian jelajahi dari filesystem. Banyak fitur yang bisa kalian coba yang sudah disediakan Laravel, semisal membuat temporary link, menghapus file, mendapatkan informasi berkas, sampai dengan manajemen direktori. Laravel sudah menyediakan panduan yang cukup jelas, maka jangan takut untuk mencobanya.

Terakhir, jika kalian merasa ada bagian yang kurang jelas, jangan sungkan untuk bertanya melalui formulir komentar. Saran dan kritik akan sangat saya apresiasi. 🙂

Tak Berkategori

Yugo Purwanto

Pemrogram PHP dan JavaScript yang sedang sibuk mengembangkan aplikasi Glosarium Bahasa Indonesia.

3 comments

  1. How to use dropzone js in normal form with laravel. i want to have a form with title,description and dropzone js to upload multiple file.

Tinggalkan Balasan