Bagian 2 dari seri
CRUD Kriteria dan Alternatif AHP Laravel
Diterbitkan 18 September 2026
Buat Controller
php artisan make:controller KriteriaController --resource
php artisan make:controller AlternatifController --resource
Route
Tambahkan ke grup middleware auth pada routes/web.php:
use App\Http\Controllers\KriteriaController;
use App\Http\Controllers\AlternatifController;
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 App\Models\PerbandinganAlternatif;
use App\Models\PerbandinganKriteria;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
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'],
]);
DB::transaction(function () use ($data) {
$kriteriaBaru = Kriteria::create($data);
$kriteriaLain = Kriteria::where(
'id',
'!=',
$kriteriaBaru->id
)->get();
foreach ($kriteriaLain as $item) {
$id1 = min($item->id, $kriteriaBaru->id);
$id2 = max($item->id, $kriteriaBaru->id);
PerbandinganKriteria::firstOrCreate(
[
'kriteria_1_id' => $id1,
'kriteria_2_id' => $id2,
],
['nilai' => 1]
);
}
$alternatif = Alternatif::orderBy('id')->get();
for ($i = 0; $i < $alternatif->count(); $i++) {
for ($j = $i + 1; $j < $alternatif->count(); $j++) {
PerbandinganAlternatif::firstOrCreate(
[
'kriteria_id' => $kriteriaBaru->id,
'alternatif_1_id' => $alternatif[$i]->id,
'alternatif_2_id' => $alternatif[$j]->id,
],
['nilai' => 1]
);
}
}
Alternatif::query()->update([
'nilai_akhir' => null,
'ranking' => null,
]);
});
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'],
]);
$kriteria->update($data);
return redirect()
->route('kriteria.index')
->with('success', 'Kriteria berhasil diperbarui.');
}
public function destroy(Kriteria $kriteria)
{
DB::transaction(function () use ($kriteria) {
$kriteria->delete();
Alternatif::query()->update([
'nilai_akhir' => null,
'ranking' => null,
]);
});
return redirect()
->route('kriteria.index')
->with('success', 'Kriteria berhasil dihapus.');
}
}
View Kriteria
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 width="100">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('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="3" class="text-center">
Belum ada data.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@endsection
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>
<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 App\Models\Kriteria;
use App\Models\PerbandinganAlternatif;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
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'],
]);
DB::transaction(function () use ($data) {
$alternatifBaru = Alternatif::create($data);
$alternatifLain = Alternatif::where(
'id',
'!=',
$alternatifBaru->id
)->orderBy('id')->get();
$kriteria = Kriteria::all();
foreach ($kriteria as $itemKriteria) {
foreach ($alternatifLain as $itemAlternatif) {
$id1 = min($itemAlternatif->id, $alternatifBaru->id);
$id2 = max($itemAlternatif->id, $alternatifBaru->id);
PerbandinganAlternatif::firstOrCreate(
[
'kriteria_id' => $itemKriteria->id,
'alternatif_1_id' => $id1,
'alternatif_2_id' => $id2,
],
['nilai' => 1]
);
}
}
Alternatif::query()->update([
'nilai_akhir' => null,
'ranking' => null,
]);
});
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);
return redirect()
->route('alternatif.index')
->with('success', 'Alternatif berhasil diperbarui.');
}
public function destroy(Alternatif $alternatif)
{
DB::transaction(function () use ($alternatif) {
$alternatif->delete();
Alternatif::query()->update([
'nilai_akhir' => null,
'ranking' => null,
]);
});
return redirect()
->route('alternatif.index')
->with('success', 'Alternatif berhasil dihapus.');
}
}
View alternatif dapat memakai pola yang sama dengan kriteria.
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">
<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>
@endsection
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 layouts/app.blade.php, tambahkan:
<a class="nav-link text-white" href="{{ route('kriteria.index') }}">Kriteria</a>
<a class="nav-link text-white" href="{{ route('alternatif.index') }}">Alternatif</a>
Data Awal
Data kriteria dan alternatif sudah dibuat melalui AhpSeeder pada bagian sebelumnya.
Jika kriteria atau alternatif baru ditambah melalui CRUD, sistem otomatis membuat pasangan perbandingannya dengan nilai awal 1.


Checkpoint
Pastikan:
- tambah, edit, dan hapus kriteria berjalan normal;
- tambah, edit, dan hapus alternatif berjalan normal;
- kriteria baru otomatis mendapatkan pasangan perbandingan;
- alternatif baru otomatis mendapatkan pasangan perbandingan pada seluruh kriteria;
- hasil ranking lama dikosongkan jika struktur kriteria atau alternatif berubah.





Komentar
Tulis Komentar