Cara Upload File ke Amazon S3 Menggunakan Laravel 7

Cara Upload File ke Amazon S3 Menggunakan Laravel 7

Pendahuluan

Mengelola data dengan format file umumnya menjadi bagian yang sering kali ditemukan pada sebuah aplikasi, sebut saja avatar atau file gambar untuk setiap users, pdf, video dan lain sebagainya. Adapun cara yang biasanya digunakan adalah menyimpannya secara langsung ke dalam server aplikasi itu sendiri dan tentu saja ini bukanlah masalah yang berarti.

Beda cerita, jika kita bekerja dengan jumlah file yang sangat banyak dan schema yang diinginkan adalah memisahkan antara server untuk sistem dan server untuk media penyimpanan. S3 (Simple Storage Service) menjadi satu dari beberapa opsi yang bisa dipilih untuk menyimpan data, sebut saja dalam hal ini adalah Amazon S3 dimana kita bisa menyimpan file ke dalamnya tanpa harus memikirkan bagaimana penyediaan infrastruktur sendiri. Melalu materi ini, kita akan belajar bagaimana meng-upload file ke Amazon S3 menggunakan Laravel 7.

Baca Juga: Membuat Aplikasi Ekspedisi Lumen 6 #5: Manage Category

Konfigurasi Laravel & Amazon S3

Tahapan pertama, install Laravel dengan command

composer create-project --prefer-dist laravel/laravel laravel-s3

Masuk ke dalam project laravel-s3 dan install package yang dibutuhkan

composer require league/flysystem-aws-s3-v3

Kita tinggalkan masalah Laravel dan beralih ke beberapa konfigurasi pada console Amazon. Buka List Bucket dan klik Create Bucket, masukkan nama bucket-nya serta region yang diinginkan.

how to create bucket amazon

Kemudian generate Access Key & Key ID, buka Security Credentials, tepatnya pada bagian tab Access Keys, klik tombol Create New Access Key. Salin informasi yang diberikan pada modal yang akan muncul dan buka file .env, kemudian masukkan key-nya pada bagian berikut

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=ap-southeast-1
AWS_BUCKET=NAMA BUCKET YANG DIBUAT

Untuk konfigurasi ke AWS-nya kita akhiri sampai disini, adapun konfigurasi lanjutannya akan dibahas kemudian. Saatnya untuk membuat fitur dari sisi Laravel, tahap awal, buat database dan masukkan informasi database-nya ke dalam file .env.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-s3
DB_USERNAME=root
DB_PASSWORD=

Buat migration baru untuk menambahkan field avatar ke dalam table users dengan command

php artisan make:migration add_field_avatar_to_users_table

Buka file migration yang baru saja di-generate dan modifikasi menjadi

<?php

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

class AddFieldAvatarToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('avatar')->nullable()->after('email');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('avatar');
        });
    }
}

Bagian terakhir, eksekusi migration yang ada dengan command php artisan migrate.

Upload File to Amazon S3

Fitur yang akan kita kerjakan sebagai panduan untuk memahami cara kerja dari fungsi upload file ke Amazon S3 ini adalah dengan membuat fitur upload & hapus data users. Generate controller baru dengan command

php artisan make:controller UserController

Buka file UserController.php dan tambahkan method berikut

public function index(Request $request)
{
    $users = User::orderBy('created_at', 'DESC')->get();
    return view('welcome', compact('users'));
}

Jangan lupa untuk menambahkan use statement di dalam file yang sama

use App\User;

Kemudian buka file welcome.blade.php dan modifikasi agar bisa menampilkan sebuah form input-an pada sisi sebelah kiri dan table untuk menampilkan data users pada sisi sebelah kanan.

<!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>Laravel S3 Amazon</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-4 mt-2">
                <div class="card">
                    <div class="card-body">
                        <form action="{{ url('/') }}" method="post" enctype="multipart/form-data">
                            @csrf
                            <div class="form-group">
                                <label for="">Nama</label>
                                <input type="text" name="name" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="">Email</label>
                                <input type="text" name="email" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="">Password</label>
                                <input type="password" name="password" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="">Avatar</label>
                                <input type="file" name="avatar" class="form-control">
                            </div>
                            <button class="btn btn-danger btn-sm">Simpan</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-md-8 mt-2">
                <div class="card">
                    <div class="card-body">
                        @if (session('success'))
                            <div class="alert alert-success">{{ session('success') }}</div>
                        @endif

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

                        <table class="table table-hover table-bordered">
                            <thead>
                                <tr>
                                    <th>#</th>
                                    <th>Nama</th>
                                    <th>Email</th>
                                    <th>Tgl</th>
                                    <th>Aksi</th>
                                </tr>
                            </thead>
                            <tbody>
                                @forelse($users as $row)
                                <tr>
                                    <td>
                                        <img src="{{ $row->avatar_url }}" alt="{{ $row->name }}" widht="100px" height="100px" class="img-responsive">
                                    </td>
                                    <td>{{ $row->name }}</td>
                                    <td>{{ $row->email }}</td>
                                    <td>{{ $row->created_at }}</td>
                                    <td>
                                        <form action="{{ url('/' . $row->id) }}" method="post">
                                            @csrf
                                            @method('DELETE')
                                            <button class="btn btn-danger btn-sm">Hapus</button>
                                        </form>
                                    </td>
                                </tr>
                                @empty
                                <tr>
                                    <td colspan="5" class="text-center">Tidak ada data</td>
                                </tr>
                                @endforelse
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

Note: Bagian yang perlu diperhatikan hanyalah pada accessor avatar_url dimana fungsi ini akan dibuat nantinya untuk memberikan url gambar yang kita upload dari Amazon.

Saatnya kita meng-handle request untuk menambahkan data user yang baru beserta file yang di-upload. Buka file UserController.php dan tambahkan code

public function store(Request $request)
{
    //VALIDASI DATA YANG DIKIRIMKAN PENGGUNA
    $this->validate($request, [
        'name' => 'required',
        'email' => 'required|email|unique:users,email',
        'password' => 'required|min:6',
        'avatar' => 'required|image|mimes:jpg,jpeg,png'
    ]);

    //JIKA FILE TERSEDIA
    if ($request->hasFile('avatar')) {
        $file = $request->file('avatar'); //MAKA KITA GET FILENYA
        //BUAT CUSTOM NAME YANG DIINGINKAN, DIMANA FORMATNYA KALI INI ADALH EMAIL + TIME DAN MENGGUNAKAN ORIGINAL EXTENSION
        $filename = $request->email . '-' . time() . '.' . $file->getClientOriginalExtension();
        //UPLOAD MENGGUNAKAN CONFIG S3, DENGAN FILE YANG DIMASUKKAN KE DALAM FOLDER IMAGES
        //SECARA OTOMATIS AMAZON AKAN MEMBUAT FOLDERNYA JIKA BELUM ADA
        Storage::disk('s3')->put('images/' . $filename, file_get_contents($file));

        //SIMPAN INFORMASI USER KE DATABASE
        //DAN AVATAR YANG DISIMPAN HANYALAH FILENAME-NYA SAJA
        User::create([
            'name' => $request->name,
            'email' => $request->email,
            'avatar' => $filename,
            'password' => bcrypt($request->password)
        ]);
        //REDIRECT KE HALAMAN YANG SAMA DAN BERIKAN NOTIFIKASI
        return redirect()->back()->with(['success' => 'Data Berhasil Disimpan']);
    }
    return redirect()->back()->with(['error' => 'Gambar Belum Dipilih']);
}

Masih dengan file yang sama, tambahkan use statement untuk Storage.

use Illuminate\Support\Facades\Storage;

Kemudian izinkan mass-assignment, buka file User.php dan ganti code berikut

protected $fillable = [
    'name', 'email', 'password', 'avatar'
];

//MENJADI
protected $guarded = [];

Definisikan routing dari method index dan store, buka file routes/web.php dan modifikasi menjadi

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', 'UserController@index');
Route::post('/', 'UserController@store');

Lakukan uji coba dengan mengisi informasi data user beserta gambar avatar-nya, dan hasil yang akan kita peroleh terlihat seperti berikut ini.

laravel s3 upload file

Kendalanya adalah pada bagian gambar, kita belum membuat Accessor untuk memberikan URL dari gambar tersebut. Tapi, jika kita cek pada Bucket yang ada di Amazon, file yang baru saja di-upload sudah tersedia.

amazon s3 upload file

Amazon Permission Access File

Ada satu masalah yang akan ditemukan dimana ketika kita mengakses filenya secara langsung melalui url: https://daengweb-s3.s3-ap-southeast-1.amazonaws.com/images/namafilegambar.png, maka yang akan ditemukan adalah format XML dengan respon bahwa akses secara publik tidak diizinkan.

Untuk mengatasi masalah ini, kita perlu membuat permission agar data yang kita miliki bisa diakses secara oleh publik. Buka halaman Policy Generator, lakukan konfigurasi seperti gambar di bawah ini

amazon s3 setup policy

Note: Pada bagian actions, pilih DeleteObject, GetObject dan PutObject. Sedangkan pada bagian ARN, masukkan arn:aws:s3:::namabucketanda/*.

Klik tombol Add Statement dan tombol Generate Policy, maka secara otomatis modal berisi konfigurasi akan ditampilkan. Salin config yang ada di modal tersebut.

Kembali ke halaman Bucket List, pilih Bucket yang kita miliki dan pada tab permission, tepatnya pada bagian Block Public Access, uncheck dua pilihan, yakni:

  1. Block public access to buckets and objects granted through new public bucket or access point policies
  2. Block public and cross-account access to buckets and objects through any public bucket or access point policies

amazon block public access

Kemudian pada bagian Bucket Policy, paste konfigurasi yang sudah disalin pada kolom yang ada dibawahnya dan klik Save.

Baca Juga: Laravel Multiple Database MySQL & MongoDB

Delete File & Accessor URL Avatar

Perihal permission file agar bisa diakses secara publik sudah selesai, maka tugas kita adalah membuat Accessor untuk menyediakan url dari gambar yang sudah diakses. Buka file User.php dan tambahkan code

protected $appends = ['avatar_url'];
public function getAvatarUrlAttribute($value)
{
    $url = 'https://'. env('AWS_BUCKET') .'.s3-'. env('AWS_DEFAULT_REGION') .'.amazonaws.com/images/';
    return $url . $this->avatar;
}

Reload browser yang kamu miliki, maka seharusnya gambar sudah ditampilkan sebagaimana mestinya. Maka tugas terkahir kita pada artikel kali adalah membuat fungsi untuk menghapus data beserta file yang ada di server Amazon.

Buka file UserController.php dan tambahkan code

public function destroy($id)
{
    $user = User::find($id); //AMBIL DATA USER BERDASARKAN ID
    Storage::disk('s3')->delete('images/' . $user->avatar); // HAPUS FILE YANG ADA DI S3, DI DALAM FOLDER IMAGES
    $user->delete(); //HAPUS DATA USER DARI DATABASE
    return redirect()->back()->with(['success' => 'Data Berhasil Dihapus']);
}

Definisikan routing dari method di atas, buka file routes/web.php dan tambahkan code

Route::delete('/{id}', 'UserController@destroy');

 

Category:
Share:

Comments