Tutorial Laravel 9 - Part #13 - Membuat CRUD dengan Fitur Upload Image

Membuat fitur upload image adalah sesuatu yang sangat menarik dan menantang untuk dipelajari bagi Programmer pemula. Karena saat mempelajari upload image ada banyak hal yang sering kali perlu diperhatikan, misal: permission tempat penyimpanan image, limitasi upload dari web server, resize image dan sebagainya.

Nah, pada kesempatan ini kita akan membahas tentang Membuat Fitur Upload Image di Laravel 9 yang sekaligus digabung dengan Create-Read data dari sebuah tabel database.

Namun sebelum membahas lebih lanjut, saya asumsikan bahwa Anda telah menginstall project laravel dari awal dan juga telah melakukan konfigurasi database. Jika belum, Anda dapat mengikuti Tutorial Instalasi Laravel 9 yang telah diposting sebelumnya.

Step 1 - Membuat Model dan Migration

Buat Mode dan Migration dengan satu perintah seperti berikut:

php artisan make:model Product -m

Ubah file migration database/migrations/xxxxx_create_products_table.php menjadi seperti berikut:

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('sku')->index();;
            $table->string('name');
            $table->decimal('price', 15, 2)->nullable();
            $table->integer('stock')->default(0);
            $table->string('image')->nullable();
            $table->timestamps();
        });
    }

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

Kita akan menyimpan informasi letak image disimpan pada field image dalam tabel products tersebut.

Ubah model Product (app/Models/Product.php) menjadi seperti berikut:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    protected $fillable = [
        'sku',
        'name',
        'price',
        'stock',
        'image',
    ];

}

Selanjutnya, jalankan migrasi database dengan perintah:

php artisan migrate

Step 2 - Membuat Controller

Buat ProductController resource dengan perintah:

php artisan make:controller ProductController -r

Perintah di atas akan membuat file app/Http/Controllers/ProductController.php yang di dalamnya terdapat method: index, create, store, show, edit, update, dan destroy.

Step 3 - Membuat Route

Buat routing (routes/web.php) yang akan mengarahkan ke method-method yang ada pada ProductController:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('products', [ProductController::class, 'index'])->name('products.index');
Route::get('products/create', [ProductController::class, 'create'])->name('products.create');
Route::post('products', [ProductController::class, 'store'])->name('products.store');

Step 4 - Membuat Validasi dengan Form Request

Kita akan membuat validasi dengan Form Request untuk StoreProductRequest dengan perintah:

php artisan make:request StoreProductRequest

Update file app/Http/Requests/StoreProductRequest.php menjadi seperti berikut:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            'sku' => ['required', 'unique:products', 'max:100'],
            'name' => ['required', 'max:100'],
            'price' => ['required', 'numeric', 'min:1'],
            'stock' => ['required', 'numeric', 'min:0'],
            'image' => 'required|image|mimes:jpeg,png,jpg|max:2048',
        ];
    }
}

Pada rules validation di atas terdapat validasi untuk inputan image yang mana image yang di upload harus berekstensi jpeg, png, atau jpg dengan ukuran maksimal 2MB.

Step 5 - Membuat Form Create Produk

Buat form sederhana untuk create produk dan simpan pada resources/views/products/create.blade.php:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My App</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  </head>
  <body>
    <div id="app">
      <div class="main-wrapper">
        <div class="main-content">
          <div class="container">
            <form method="post" action="{{ route('products.store') }}" enctype="multipart/form-data">
              @csrf
              <div class="card mt-5">
                <div class="card-header">
                  <h3>New Product</h3>
                </div>
                <div class="card-body">
                    @if ($errors->any())
                      <div class="alert alert-danger">
                        <div class="alert-title"><h4>Whoops!</h4></div>
                          There are some problems with your input.
                          <ul>
                            @foreach ($errors->all() as $error)
                              <li>{{ $error }}</li>
                            @endforeach
                          </ul>
                      </div> 
                    @endif

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

                    @if (session('error'))
                      <div class="alert alert-danger">{{ session('error') }}</div>
                    @endif
                    <div class="mb-3">
                      <label class="form-label">SKU</label>
                      <input type="text" class="form-control" name="sku" value="{{ old('sku') }}" placeholder="#SKU">
                    </div>
                    <div class="mb-3">
                      <label class="form-label">Name</label>
                      <input type="text" class="form-control" name="name" value="{{ old('name') }}"  placeholder="Name">
                    </div>
                    <div class="mb-3">
                      <label class="form-label">Price</label>
                      <input type="text" class="form-control" name="price" value="{{ old('price') }}"  placeholder="Price">
                    </div>
                    <div class="mb-3">
                      <label class="form-label">Stock</label>
                      <input type="text" class="form-control" name="stock" value="{{ old('stock') }}"  placeholder="Stock">
                    </div>
                    <div class="mb-3">
                      <label for="formFile" class="form-label">Image</label>
                      <input class="form-control" type="file" name="image" id="formFile">
                    </div>
                </div>
                <div class="card-footer">
                  <button class="btn btn-primary" type="submit">Create</button>
                </div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Kemudian update method create pada ProductController menjadi seperti berikut:

	public function create()
    {
        return view('products.create');
    }

Tampilan form create produk:

Step 6 - Membuat Fungsi Store Produk dengan Upload Image

Ini adalah proses menyimpan input dari form produk ke dalam tabel products di database. Proses ini akan dihandle oleh method store yang ada di dalam ProductController. Pertama, kita perlu melakukan validasi input dengan StoreProductRequest yang telah kita buat di atas. Kemudian, jika inputan tersebut lolos validasi maka langkah selanjutnya adalah proses upload image ke directory yang ada di server/aplikasi kita dalam hal ini adalah public/images. Kode program method store akan menjadi seperti berikut:

public function store(StoreProductRequest $request)
    {
        $imageName = time().'.'.$request->image->extension();
        $uploadedImage = $request->image->move(public_path('images'), $imageName);
        $imagePath = 'images/' . $imageName;

        $params = $request->validated();
        
        if ($product = Product::create($params)) {
            $product->image = $imagePath;
            $product->save();

            return redirect(route('products.index'))->with('success', 'Added!');
        }
    }

Kita ada beberapa pilihan untuk tujuan penyimpanan image/file di Laravel yaitu di public folder, storage folder dan s3.

Upload ke Public Folder

Contoh di atas adalah kode program untuk upload ke public folder dengan membuat sub folder bernama images yaitu baris kode berikut:

$request->image->move(public_path('images'), $imageName);
// public/images/image_name.png
Upload ke Storage Folder

Kode program untuk upload image ke storage folder adalah seperti berikut:

$request->image->storeAs('images', $imageName);
// storage/app/images/image_name.png

Namun ada catatan yaitu jika kita ingin menampilkan gambar yang disimpan pada storage folder ini, kita perlu membuat symbolic link dengan cara seperti berikut:

php artisan storage:link
Upload ke S3

Laravel juga memberikan kemudahan bagi kita untuk menyimpan image yang di upload ke layanan cloud storage yang mendukung protocol S3. Kode programnya adalah seperti berikut:

$request->image->storeAs('images', $imageName, 's3');

Tentu dengan pengaturan credential yang dibutuhkan diantaranya AccessID, AccessSecret dan BucketName.

Step 7 - Membuat Fungsi Read / List Produk

Untuk menampilkan data produk ini akan dihandle oleh method index dalam ProductController dengan kode program seperti berikut:

	public function index()
    {
        $products = Product::all();

        return view('products.index', ['products' => $products]);
    }

Pertama, panggil Product::all() untuk melakukan query mengambil semua data dalam tabel products. Selanjutnya data tersebut akan di assign sebagai parameter pada saat render view resources/views/products/index.blade.php.

Adapun kode program untuk index.blade.php adalah seperti berikut:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My App</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  </head>
  <body>
    <div id="app">
      <div class="main-wrapper">
        <div class="main-content">
          <div class="container">
            <div class="card mt-5">
              <div class="card-header">
                <h3>List Product</h3>
              </div>
              <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
                <p>
                  <a class="btn btn-primary" href="{{ route('products.create') }}">New Product</a>
                </p>
                <table class="table table-striped table-bordered">
                  <thead>
                    <tr>
                      <th>Image</th>
                      <th>ID</th>
                      <th>SKU</th>
                      <th>Name</th>
                      <th>Price</th>
                      <th>Stock</th>
                    </tr>
                  </thead>
                  <tbody>
                    @forelse ($products as $product)
                      <tr>
                        <td><img src="{{ asset($product->image) }}" class="img-thumbnail" style="width:200px" /></td>
                        <td>{{ $product->id }}</td>
                        <td>{{ $product->sku }}</td>
                        <td>{{ $product->name }}</td>
                        <td>{{ $product->price }}</td>
                        <td>{{ $product->stock }}</td>
                      </tr>
                    @empty
                      <tr>
                        <td colspan="6">
                            No record found!
                        </td>
                      </tr>
                    @endforelse
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Jika tidak ada permasalahan saat upload image, maka tampilan list produk harusnya menyertakan image seperti berikut:

Download Source Code

Source code lengkap dapat di download dari github: https://github.com/gieart87/tutorial-laravel9/tree/feature/laravel9-crud-with-upload-image

Anda tertantang untuk membuat fitur upload image? Selamat mencoba!

Tulis Komentar