Bagian 2 dari seri

CRUD Kriteria dan Alternatif TOPSIS Laravel

Diterbitkan 21 September 2026

Pada bagian ini buat CRUD kriteria dan alternatif. Kriteria memiliki bobot dan atribut.

Gunakan benefit jika nilai besar lebih baik, dan cost jika nilai kecil lebih baik.

Buat Controller

php artisan make:controller KriteriaController --resource
php artisan make:controller AlternatifController --resource

Route

Tambahkan ke dalam grup middleware auth pada routes/web.php:

use App\Http\Controllers\AlternatifController;
use App\Http\Controllers\KriteriaController;

Route::resource(
    'kriteria',
    KriteriaController::class
)
    ->except('show')
    ->parameters(['kriteria' => 'kriteria']);

Route::resource(
    'alternatif',
    AlternatifController::class
)->except('show');

KriteriaController

app/Http/Controllers/KriteriaController.php:

<?php

namespace App\Http\Controllers;

use App\Models\Alternatif;
use App\Models\Kriteria;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class KriteriaController extends Controller
{
    public function index()
    {
        $items = Kriteria::orderBy('kode')->get();

        return view('kriteria.index', compact('items'));
    }

    public function create()
    {
        return view('kriteria.form', [
            'item' => new Kriteria(),
        ]);
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'kode' => [
                'required',
                'max:10',
                'unique:kriteria,kode',
            ],
            'nama' => [
                'required',
                'max:255',
            ],
            'bobot' => [
                'required',
                'numeric',
                'gt:0',
            ],
            'atribut' => [
                'required',
                Rule::in(['benefit', 'cost']),
            ],
        ]);

        Kriteria::create($data);

        $this->resetHasil();

        return redirect()
            ->route('kriteria.index')
            ->with(
                'success',
                'Kriteria berhasil ditambahkan.'
            );
    }

    public function edit(Kriteria $kriteria)
    {
        return view('kriteria.form', [
            'item' => $kriteria,
        ]);
    }

    public function update(
        Request $request,
        Kriteria $kriteria
    ) {
        $data = $request->validate([
            'kode' => [
                'required',
                'max:10',
                Rule::unique('kriteria', 'kode')
                    ->ignore($kriteria->id),
            ],
            'nama' => [
                'required',
                'max:255',
            ],
            'bobot' => [
                'required',
                'numeric',
                'gt:0',
            ],
            'atribut' => [
                'required',
                Rule::in(['benefit', 'cost']),
            ],
        ]);

        $kriteria->update($data);

        $this->resetHasil();

        return redirect()
            ->route('kriteria.index')
            ->with(
                'success',
                'Kriteria berhasil diperbarui.'
            );
    }

    public function destroy(Kriteria $kriteria)
    {
        $kriteria->delete();

        $this->resetHasil();

        return redirect()
            ->route('kriteria.index')
            ->with(
                'success',
                'Kriteria berhasil dihapus.'
            );
    }

    private function resetHasil(): void
    {
        Alternatif::query()->update([
            'nilai_preferensi' => null,
            'ranking' => null,
        ]);
    }
}

View Daftar Kriteria

Buat resources/views/kriteria/index.blade.php:

@extends('layouts.app')

@section('title', 'Kriteria')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
    <h1 class="h4 mb-0">Kriteria</h1>

    <a
        href="{{ route('kriteria.create') }}"
        class="btn btn-primary">
        Tambah Kriteria
    </a>
</div>

<div class="card border-0 shadow-sm">
    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-bordered align-middle mb-0">
                <thead>
                <tr>
                    <th>Kode</th>
                    <th>Nama</th>
                    <th>Bobot</th>
                    <th>Atribut</th>
                    <th width="160">Aksi</th>
                </tr>
                </thead>

                <tbody>
                @forelse($items as $item)
                    <tr>
                        <td>{{ $item->kode }}</td>
                        <td>{{ $item->nama }}</td>
                        <td>{{ $item->bobot }}</td>
                        <td>
                            <span class="badge text-bg-secondary">
                                {{ ucfirst($item->atribut) }}
                            </span>
                        </td>
                        <td>
                            <a
                                href="{{ route('kriteria.edit', $item) }}"
                                class="btn btn-warning btn-sm">
                                Edit
                            </a>

                            <form
                                method="POST"
                                action="{{ route('kriteria.destroy', $item) }}"
                                class="d-inline"
                                onsubmit="return confirm('Hapus data?')">
                                @csrf
                                @method('DELETE')

                                <button class="btn btn-danger btn-sm">
                                    Hapus
                                </button>
                            </form>
                        </td>
                    </tr>
                @empty
                    <tr>
                        <td
                            colspan="5"
                            class="text-center">
                            Belum ada data.
                        </td>
                    </tr>
                @endforelse
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection

Form Kriteria

Buat resources/views/kriteria/form.blade.php:

@extends('layouts.app')

@section(
    'title',
    $item->exists ? 'Edit Kriteria' : 'Tambah Kriteria'
)

@section('content')
<div class="card border-0 shadow-sm">
    <div class="card-body">
        <h1 class="h4 mb-4">
            {{ $item->exists ? 'Edit Kriteria' : 'Tambah Kriteria' }}
        </h1>

        <form
            method="POST"
            action="{{ $item->exists
                ? route('kriteria.update', $item)
                : route('kriteria.store') }}">

            @csrf

            @if($item->exists)
                @method('PUT')
            @endif

            <div class="mb-3">
                <label class="form-label">Kode</label>

                <input
                    type="text"
                    name="kode"
                    value="{{ old('kode', $item->kode) }}"
                    class="form-control"
                    required>
            </div>

            <div class="mb-3">
                <label class="form-label">Nama Kriteria</label>

                <input
                    type="text"
                    name="nama"
                    value="{{ old('nama', $item->nama) }}"
                    class="form-control"
                    required>
            </div>

            <div class="mb-3">
                <label class="form-label">Bobot</label>

                <input
                    type="number"
                    name="bobot"
                    value="{{ old('bobot', $item->bobot) }}"
                    class="form-control"
                    step="0.000001"
                    min="0.000001"
                    required>

                <div class="form-text">
                    Bobot boleh menggunakan 30, 40, 30
                    atau 0.3, 0.4, 0.3.
                    Sistem akan menormalisasi bobot.
                </div>
            </div>

            <div class="mb-3">
                <label class="form-label">Atribut</label>

                <select
                    name="atribut"
                    class="form-select"
                    required>

                    <option value="">Pilih atribut</option>

                    <option
                        value="benefit"
                        @selected(
                            old('atribut', $item->atribut) === 'benefit'
                        )>
                        Benefit
                    </option>

                    <option
                        value="cost"
                        @selected(
                            old('atribut', $item->atribut) === 'cost'
                        )>
                        Cost
                    </option>
                </select>
            </div>

            <button class="btn btn-primary">
                Simpan
            </button>

            <a
                href="{{ route('kriteria.index') }}"
                class="btn btn-secondary">
                Kembali
            </a>
        </form>
    </div>
</div>
@endsection

AlternatifController

app/Http/Controllers/AlternatifController.php:

<?php

namespace App\Http\Controllers;

use App\Models\Alternatif;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class AlternatifController extends Controller
{
    public function index()
    {
        $items = Alternatif::orderBy('kode')->get();

        return view('alternatif.index', compact('items'));
    }

    public function create()
    {
        return view('alternatif.form', [
            'item' => new Alternatif(),
        ]);
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'kode' => [
                'required',
                'max:10',
                'unique:alternatif,kode',
            ],
            'nama' => [
                'required',
                'max:255',
            ],
        ]);

        Alternatif::create($data);

        $this->resetHasil();

        return redirect()
            ->route('alternatif.index')
            ->with(
                'success',
                'Alternatif berhasil ditambahkan.'
            );
    }

    public function edit(Alternatif $alternatif)
    {
        return view('alternatif.form', [
            'item' => $alternatif,
        ]);
    }

    public function update(
        Request $request,
        Alternatif $alternatif
    ) {
        $data = $request->validate([
            'kode' => [
                'required',
                'max:10',
                Rule::unique('alternatif', 'kode')
                    ->ignore($alternatif->id),
            ],
            'nama' => [
                'required',
                'max:255',
            ],
        ]);

        $alternatif->update($data);

        $this->resetHasil();

        return redirect()
            ->route('alternatif.index')
            ->with(
                'success',
                'Alternatif berhasil diperbarui.'
            );
    }

    public function destroy(Alternatif $alternatif)
    {
        $alternatif->delete();

        $this->resetHasil();

        return redirect()
            ->route('alternatif.index')
            ->with(
                'success',
                'Alternatif berhasil dihapus.'
            );
    }

    private function resetHasil(): void
    {
        Alternatif::query()->update([
            'nilai_preferensi' => null,
            'ranking' => null,
        ]);
    }
}

View Daftar Alternatif

Buat resources/views/alternatif/index.blade.php:

@extends('layouts.app')

@section('title', 'Alternatif')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
    <h1 class="h4 mb-0">Alternatif</h1>

    <a
        href="{{ route('alternatif.create') }}"
        class="btn btn-primary">
        Tambah Alternatif
    </a>
</div>

<div class="card border-0 shadow-sm">
    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-bordered align-middle mb-0">
                <thead>
                <tr>
                    <th>Kode</th>
                    <th>Nama</th>
                    <th width="160">Aksi</th>
                </tr>
                </thead>

                <tbody>
                @forelse($items as $item)
                    <tr>
                        <td>{{ $item->kode }}</td>
                        <td>{{ $item->nama }}</td>
                        <td>
                            <a
                                href="{{ route('alternatif.edit', $item) }}"
                                class="btn btn-warning btn-sm">
                                Edit
                            </a>

                            <form
                                method="POST"
                                action="{{ route('alternatif.destroy', $item) }}"
                                class="d-inline"
                                onsubmit="return confirm('Hapus data?')">
                                @csrf
                                @method('DELETE')

                                <button class="btn btn-danger btn-sm">
                                    Hapus
                                </button>
                            </form>
                        </td>
                    </tr>
                @empty
                    <tr>
                        <td
                            colspan="3"
                            class="text-center">
                            Belum ada data.
                        </td>
                    </tr>
                @endforelse
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection

Form Alternatif

Buat resources/views/alternatif/form.blade.php:

@extends('layouts.app')

@section(
    'title',
    $item->exists ? 'Edit Alternatif' : 'Tambah Alternatif'
)

@section('content')
<div class="card border-0 shadow-sm">
    <div class="card-body">
        <h1 class="h4 mb-4">
            {{ $item->exists ? 'Edit Alternatif' : 'Tambah Alternatif' }}
        </h1>

        <form
            method="POST"
            action="{{ $item->exists
                ? route('alternatif.update', $item)
                : route('alternatif.store') }}">

            @csrf

            @if($item->exists)
                @method('PUT')
            @endif

            <div class="mb-3">
                <label class="form-label">Kode</label>

                <input
                    type="text"
                    name="kode"
                    value="{{ old('kode', $item->kode) }}"
                    class="form-control"
                    required>
            </div>

            <div class="mb-3">
                <label class="form-label">Nama Alternatif</label>

                <input
                    type="text"
                    name="nama"
                    value="{{ old('nama', $item->nama) }}"
                    class="form-control"
                    required>
            </div>

            <button class="btn btn-primary">
                Simpan
            </button>

            <a
                href="{{ route('alternatif.index') }}"
                class="btn btn-secondary">
                Kembali
            </a>
        </form>
    </div>
</div>
@endsection

Tambahkan Menu

Pada navbar resources/views/layouts/app.blade.php, di area @auth, tambahkan menu:

<a
    class="nav-link text-white"
    href="{{ route('kriteria.index') }}">
    Kriteria
</a>

<a
    class="nav-link text-white"
    href="{{ route('alternatif.index') }}">
    Alternatif
</a>

Contoh

Gunakan data:

Harga      = cost
Kualitas   = benefit
Pelayanan  = benefit
Harga      = cost
Kualitas   = benefit
Pelayanan  = benefit
Tutorial Topsis Laravel Kriteria
Tutorial Topsis Laravel Kriteria Tambah
Tutorial Topsis Laravel Alternatif

Checkpoint

Pastikan:

  • tambah kriteria berhasil;
  • edit kriteria berhasil;
  • hapus kriteria berhasil;
  • bobot wajib lebih besar dari 0;
  • atribut hanya benefit atau cost;
  • tambah alternatif berhasil;
  • edit alternatif berhasil;
  • hapus alternatif berhasil;
  • perubahan master mengosongkan hasil ranking lama.

Lanjut: Input Nilai Alternatif pada Setiap Kriteria