Bagian 5 dari seri

Hasil Nilai Preferensi dan Ranking SAW Laravel

Diterbitkan 21 September 2026

Tutorial Saw Laravel Hitung Hasil

Bagian terakhir membuat halaman hasil, detail perhitungan, dan ranking SAW.

Buat Controller

php artisan make:controller HasilController

HasilController

app/Http/Controllers/HasilController.php:

<?php

namespace App\Http\Controllers;

use App\Models\Alternatif;
use App\Models\Kriteria;
use App\Models\Penilaian;
use App\Services\SawService;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class HasilController extends Controller
{
    public function index(SawService $saw)
    {
        $kriteria = Kriteria::orderBy('kode')->get();
        $alternatif = Alternatif::orderBy('kode')->get();

        $ranking = Alternatif::orderByRaw(
            'ranking IS NULL, ranking ASC'
        )
            ->orderBy('kode')
            ->get();

        $detail = $this->buildDetail(
            $saw,
            $kriteria,
            $alternatif
        );

        return view('hasil.index', compact(
            'ranking',
            'kriteria',
            'alternatif',
            'detail'
        ));
    }

    public function calculate(SawService $saw)
    {
        $kriteria = Kriteria::orderBy('kode')->get();
        $alternatif = Alternatif::orderBy('kode')->get();

        if ($kriteria->isEmpty()) {
            return redirect()
                ->route('hasil.index')
                ->withErrors('Kriteria belum tersedia.');
        }

        if ($alternatif->count() < 2) {
            return redirect()
                ->route('hasil.index')
                ->withErrors('Minimal diperlukan dua alternatif.');
        }

        $penilaian = Penilaian::all();

        $expected =
            $kriteria->count()
            * $alternatif->count();

        if ($penilaian->count() !== $expected) {
            return redirect()
                ->route('hasil.index')
                ->withErrors('Data penilaian belum lengkap.');
        }

        $weights = [];
        $attributes = [];
        $matrix = [];

        $nilaiMap = $penilaian->keyBy(function ($item) {
            return $item->alternatif_id
                . '-'
                . $item->kriteria_id;
        });

        foreach ($kriteria as $itemKriteria) {
            $weights[$itemKriteria->id] =
                (float) $itemKriteria->bobot;

            $attributes[$itemKriteria->id] =
                $itemKriteria->atribut;
        }

        foreach ($alternatif as $itemAlternatif) {
            foreach ($kriteria as $itemKriteria) {
                $key =
                    $itemAlternatif->id
                    . '-'
                    . $itemKriteria->id;

                if (! $nilaiMap->has($key)) {
                    return redirect()
                        ->route('hasil.index')
                        ->withErrors('Data penilaian belum lengkap.');
                }

                $matrix[$itemAlternatif->id]
                    [$itemKriteria->id] =
                    (float) $nilaiMap[$key]->nilai;
            }
        }

        try {
            $hasil = $saw->calculate(
                $matrix,
                $weights,
                $attributes
            );
        } catch (InvalidArgumentException $e) {
            return redirect()
                ->route('hasil.index')
                ->withErrors($e->getMessage());
        }

        $preferences = $hasil['preferences'];

        arsort($preferences);

        DB::transaction(function () use ($preferences) {
            $position = 0;
            $ranking = 0;
            $previousValue = null;
            $epsilon = 0.0000000001;

            foreach ($preferences as $alternatifId => $value) {
                $position++;

                if (
                    $previousValue === null
                    || abs($value - $previousValue) > $epsilon
                ) {
                    $ranking = $position;
                }

                Alternatif::whereKey($alternatifId)->update([
                    'nilai_preferensi' => $value,
                    'ranking' => $ranking,
                ]);

                $previousValue = $value;
            }
        });

        return redirect()
            ->route('hasil.index')
            ->with(
                'success',
                'Perhitungan SAW dan ranking berhasil dilakukan.'
            );
    }

    private function buildDetail(
        SawService $saw,
        $kriteria,
        $alternatif
    ): ?array {
        if ($kriteria->isEmpty() || $alternatif->isEmpty()) {
            return null;
        }

        $penilaian = Penilaian::all();

        $expected =
            $kriteria->count()
            * $alternatif->count();

        if ($penilaian->count() !== $expected) {
            return null;
        }

        $weights = [];
        $attributes = [];
        $matrix = [];

        $nilaiMap = $penilaian->keyBy(function ($item) {
            return $item->alternatif_id
                . '-'
                . $item->kriteria_id;
        });

        foreach ($kriteria as $itemKriteria) {
            $weights[$itemKriteria->id] =
                (float) $itemKriteria->bobot;

            $attributes[$itemKriteria->id] =
                $itemKriteria->atribut;
        }

        foreach ($alternatif as $itemAlternatif) {
            foreach ($kriteria as $itemKriteria) {
                $key =
                    $itemAlternatif->id
                    . '-'
                    . $itemKriteria->id;

                if (! $nilaiMap->has($key)) {
                    return null;
                }

                $matrix[$itemAlternatif->id]
                    [$itemKriteria->id] =
                    (float) $nilaiMap[$key]->nilai;
            }
        }

        try {
            $hasil = $saw->calculate(
                $matrix,
                $weights,
                $attributes
            );
        } catch (InvalidArgumentException) {
            return null;
        }

        $hasil['matrix'] = $matrix;

        return $hasil;
    }
}

Route

Tambahkan pada grup auth:

use App\Http\Controllers\HasilController;

Route::get(
    '/hasil',
    [HasilController::class, 'index']
)->name('hasil.index');

Route::post(
    '/hasil/hitung',
    [HasilController::class, 'calculate']
)->name('hasil.calculate');

Tambahkan Menu

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

View Hasil

resources/views/hasil/index.blade.php:

@extends('layouts.app')

@section('title', 'Hasil SAW')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
    <div>
        <h2 class="h4 mb-1">Hasil Ranking SAW</h2>

        <p class="text-muted mb-0">
            Nilai preferensi terbesar mendapat ranking tertinggi.
        </p>
    </div>

    <form method="POST" action="{{ route('hasil.calculate') }}">
        @csrf

        <button class="btn btn-primary">
            Hitung SAW
        </button>
    </form>
</div>

<div class="card border-0 shadow-sm mb-4">
    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-bordered table-striped align-middle mb-0">
                <thead>
                <tr>
                    <th width="100">Ranking</th>
                    <th>Kode</th>
                    <th>Alternatif</th>
                    <th>Nilai Preferensi</th>
                </tr>
                </thead>

                <tbody>
                @forelse($ranking as $item)
                    <tr>
                        <td>
                            @if($item->ranking)
                                <span class="badge text-bg-primary">
                                    {{ $item->ranking }}
                                </span>
                            @else
                                -
                            @endif
                        </td>

                        <td>{{ $item->kode }}</td>
                        <td>{{ $item->nama }}</td>

                        <td>
                            @if($item->nilai_preferensi !== null)
                                {{ number_format(
                                    $item->nilai_preferensi,
                                    6
                                ) }}
                            @else
                                Belum dihitung
                            @endif
                        </td>
                    </tr>
                @empty
                    <tr>
                        <td colspan="4" class="text-center">
                            Belum ada alternatif.
                        </td>
                    </tr>
                @endforelse
                </tbody>
            </table>
        </div>
    </div>
</div>

@if($detail)

<h2 class="h5 mt-4">1. Matriks Keputusan</h2>

<div class="table-responsive mb-4">
    <table class="table table-bordered text-center align-middle">
        <thead>
        <tr>
            <th>Alternatif</th>

            @foreach($kriteria as $itemKriteria)
                <th>
                    {{ $itemKriteria->kode }}
                    <div class="small fw-normal">
                        {{ ucfirst($itemKriteria->atribut) }}
                    </div>
                </th>
            @endforeach
        </tr>
        </thead>

        <tbody>
        @foreach($alternatif as $itemAlternatif)
            <tr>
                <th>{{ $itemAlternatif->kode }}</th>

                @foreach($kriteria as $itemKriteria)
                    <td>
                        {{ number_format(
                            $detail['matrix'][$itemAlternatif->id][$itemKriteria->id],
                            6
                        ) }}
                    </td>
                @endforeach
            </tr>
        @endforeach
        </tbody>
    </table>
</div>

<h2 class="h5">2. Bobot Normalisasi</h2>

<div class="table-responsive mb-4">
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>Kriteria</th>
            <th>Atribut</th>
            <th>Bobot Awal</th>
            <th>Bobot Normalisasi</th>
        </tr>
        </thead>

        <tbody>
        @foreach($kriteria as $itemKriteria)
            <tr>
                <td>{{ $itemKriteria->kode }} - {{ $itemKriteria->nama }}</td>
                <td>{{ ucfirst($itemKriteria->atribut) }}</td>
                <td>{{ $itemKriteria->bobot }}</td>
                <td>
                    {{ number_format(
                        $detail['weights'][$itemKriteria->id],
                        6
                    ) }}
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>
</div>

<h2 class="h5">3. Matriks Normalisasi</h2>

<div class="table-responsive mb-4">
    <table class="table table-bordered text-center align-middle">
        <thead>
        <tr>
            <th>Alternatif</th>

            @foreach($kriteria as $itemKriteria)
                <th>{{ $itemKriteria->kode }}</th>
            @endforeach
        </tr>
        </thead>

        <tbody>
        @foreach($alternatif as $itemAlternatif)
            <tr>
                <th>{{ $itemAlternatif->kode }}</th>

                @foreach($kriteria as $itemKriteria)
                    <td>
                        {{ number_format(
                            $detail['normalized'][$itemAlternatif->id][$itemKriteria->id],
                            6
                        ) }}
                    </td>
                @endforeach
            </tr>
        @endforeach
        </tbody>
    </table>
</div>

<h2 class="h5">4. Nilai Preferensi</h2>

<div class="table-responsive mb-4">
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>Alternatif</th>
            <th>Nilai Preferensi</th>
        </tr>
        </thead>

        <tbody>
        @foreach($alternatif as $itemAlternatif)
            <tr>
                <td>
                    {{ $itemAlternatif->kode }}
                    -
                    {{ $itemAlternatif->nama }}
                </td>

                <td>
                    {{ number_format(
                        $detail['preferences'][$itemAlternatif->id],
                        6
                    ) }}
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>
</div>

@endif
@endsection

Route Final

Pastikan routes/web.php berisi:

<?php

use App\Http\Controllers\AlternatifController;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\HasilController;
use App\Http\Controllers\KriteriaController;
use App\Http\Controllers\PenilaianController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return redirect()->route('dashboard');
});

Route::middleware('guest')->group(function () {
    Route::get('/login', [AuthController::class, 'create'])
        ->name('login');

    Route::post('/login', [AuthController::class, 'store'])
        ->middleware('throttle:5,1')
        ->name('login.store');
});

Route::middleware('auth')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])
        ->name('dashboard');

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

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

    Route::get('/penilaian', [PenilaianController::class, 'index'])
        ->name('penilaian.index');

    Route::post('/penilaian', [PenilaianController::class, 'store'])
        ->name('penilaian.store');

    Route::get('/hasil', [HasilController::class, 'index'])
        ->name('hasil.index');

    Route::post('/hasil/hitung', [HasilController::class, 'calculate'])
        ->name('hasil.calculate');

    Route::post('/logout', [AuthController::class, 'destroy'])
        ->name('logout');
});

Menu Final

Pada navbar:

@auth
<div class="d-flex align-items-center gap-3">
    <a class="nav-link text-white"
       href="{{ route('kriteria.index') }}">
        Kriteria
    </a>

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

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

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

    <span class="text-white">
        {{ auth()->user()->name }}
    </span>

    <form method="POST" action="{{ route('logout') }}">
        @csrf

        <button class="btn btn-outline-light btn-sm">
            Logout
        </button>
    </form>
</div>
@endauth

Jalankan Project

Jalankan:

php artisan migrate:fresh --seed
php artisan serve

Buka:

http://127.0.0.1:8000

Login:

Email    : admin@example.com
Password : admin12345
Tutorial Saw Laravel Hitung Hasil
Tutorial Saw Laravel Hitung Detail

Pengujian Akhir

Gunakan data contoh dari tutorial, klik Hitung SAW, lalu pastikan:

  • matriks keputusan tampil;
  • bobot normalisasi tampil;
  • matriks normalisasi tampil;
  • nilai preferensi tampil;
  • ranking tersimpan;
  • ranking diurutkan dari nilai preferensi terbesar.

Selesai

Aplikasi SAW Laravel sudah memiliki alur lengkap:

Login
↓
Kriteria
↓
Alternatif
↓
Penilaian
↓
Normalisasi
↓
Nilai Preferensi
↓
Ranking

Kembali ke Tutorial SAW Laravel

Komentar

Belum ada komentar.

Tulis Komentar

Komentar pengunjung diperiksa sebelum ditampilkan.