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



Checkpoint
Pastikan:
- tambah kriteria berhasil;
- edit kriteria berhasil;
- hapus kriteria berhasil;
- bobot wajib lebih besar dari
0; - atribut hanya
benefitataucost; - tambah alternatif berhasil;
- edit alternatif berhasil;
- hapus alternatif berhasil;
- perubahan master mengosongkan hasil ranking lama.




