Bagian 4 dari seri
Perhitungan TOPSIS dengan Laravel: Normalisasi, Solusi Ideal, dan Nilai Preferensi
Diterbitkan 21 September 2026
Pada bagian ini buat TopsisService. Salin kode berikut apa adanya.
Tahapan Perhitungan
Service akan menangani:
- normalisasi bobot;
- normalisasi matriks keputusan;
- matriks normalisasi terbobot;
- solusi ideal positif;
- solusi ideal negatif;
- jarak terhadap solusi ideal;
- nilai preferensi.
Buat Folder Service
Buat folder:
app/Services
Lalu buat:
app/Services/TopsisService.php
TopsisService
Isi:
<?php
namespace App\Services;
use InvalidArgumentException;
class TopsisService
{
public function calculate(
array $matrix,
array $weights,
array $attributes
): array {
if (empty($matrix)) {
throw new InvalidArgumentException(
'Matriks keputusan tidak boleh kosong.'
);
}
$alternativeIds = array_keys($matrix);
$criteriaIds = array_keys(reset($matrix));
$normalizedWeights = $this->normalizeWeights(
$weights,
$criteriaIds
);
[$normalized, $divisors] = $this->normalizeMatrix(
$matrix,
$alternativeIds,
$criteriaIds
);
$weighted = $this->weightedMatrix(
$normalized,
$normalizedWeights,
$alternativeIds,
$criteriaIds
);
[$idealPositive, $idealNegative] =
$this->idealSolutions(
$weighted,
$attributes,
$alternativeIds,
$criteriaIds
);
[$distancePositive, $distanceNegative] =
$this->distances(
$weighted,
$idealPositive,
$idealNegative,
$alternativeIds,
$criteriaIds
);
$preferences = $this->preferences(
$distancePositive,
$distanceNegative,
$alternativeIds
);
return [
'weights' => $normalizedWeights,
'divisors' => $divisors,
'normalized' => $normalized,
'weighted' => $weighted,
'idealPositive' => $idealPositive,
'idealNegative' => $idealNegative,
'distancePositive' => $distancePositive,
'distanceNegative' => $distanceNegative,
'preferences' => $preferences,
];
}
public function normalizeWeights(
array $weights,
array $criteriaIds
): array {
$total = 0.0;
foreach ($criteriaIds as $criteriaId) {
$weight = (float) ($weights[$criteriaId] ?? 0);
if ($weight <= 0) {
throw new InvalidArgumentException(
'Semua bobot kriteria harus lebih besar dari 0.'
);
}
$total += $weight;
}
if ($total <= 0) {
throw new InvalidArgumentException(
'Total bobot harus lebih besar dari 0.'
);
}
$result = [];
foreach ($criteriaIds as $criteriaId) {
$result[$criteriaId] =
(float) $weights[$criteriaId] / $total;
}
return $result;
}
public function normalizeMatrix(
array $matrix,
array $alternativeIds,
array $criteriaIds
): array {
$divisors = [];
foreach ($criteriaIds as $criteriaId) {
$sumSquares = 0.0;
foreach ($alternativeIds as $alternativeId) {
$value = (float)
($matrix[$alternativeId][$criteriaId] ?? 0);
$sumSquares += $value ** 2;
}
$divisor = sqrt($sumSquares);
if ($divisor == 0.0) {
throw new InvalidArgumentException(
"Pembagi normalisasi kriteria ID {$criteriaId} bernilai 0."
);
}
$divisors[$criteriaId] = $divisor;
}
$normalized = [];
foreach ($alternativeIds as $alternativeId) {
foreach ($criteriaIds as $criteriaId) {
$normalized[$alternativeId][$criteriaId] =
(float) $matrix[$alternativeId][$criteriaId]
/ $divisors[$criteriaId];
}
}
return [$normalized, $divisors];
}
public function weightedMatrix(
array $normalized,
array $weights,
array $alternativeIds,
array $criteriaIds
): array {
$weighted = [];
foreach ($alternativeIds as $alternativeId) {
foreach ($criteriaIds as $criteriaId) {
$weighted[$alternativeId][$criteriaId] =
$normalized[$alternativeId][$criteriaId]
* $weights[$criteriaId];
}
}
return $weighted;
}
public function idealSolutions(
array $weighted,
array $attributes,
array $alternativeIds,
array $criteriaIds
): array {
$idealPositive = [];
$idealNegative = [];
foreach ($criteriaIds as $criteriaId) {
$values = [];
foreach ($alternativeIds as $alternativeId) {
$values[] =
$weighted[$alternativeId][$criteriaId];
}
$attribute =
$attributes[$criteriaId] ?? null;
if ($attribute === 'benefit') {
$idealPositive[$criteriaId] = max($values);
$idealNegative[$criteriaId] = min($values);
} elseif ($attribute === 'cost') {
$idealPositive[$criteriaId] = min($values);
$idealNegative[$criteriaId] = max($values);
} else {
throw new InvalidArgumentException(
"Atribut kriteria ID {$criteriaId} tidak valid."
);
}
}
return [$idealPositive, $idealNegative];
}
public function distances(
array $weighted,
array $idealPositive,
array $idealNegative,
array $alternativeIds,
array $criteriaIds
): array {
$distancePositive = [];
$distanceNegative = [];
foreach ($alternativeIds as $alternativeId) {
$sumPositive = 0.0;
$sumNegative = 0.0;
foreach ($criteriaIds as $criteriaId) {
$value =
$weighted[$alternativeId][$criteriaId];
$sumPositive +=
($value - $idealPositive[$criteriaId]) ** 2;
$sumNegative +=
($value - $idealNegative[$criteriaId]) ** 2;
}
$distancePositive[$alternativeId] =
sqrt($sumPositive);
$distanceNegative[$alternativeId] =
sqrt($sumNegative);
}
return [
$distancePositive,
$distanceNegative,
];
}
public function preferences(
array $distancePositive,
array $distanceNegative,
array $alternativeIds
): array {
$preferences = [];
foreach ($alternativeIds as $alternativeId) {
$dPlus =
$distancePositive[$alternativeId];
$dMinus =
$distanceNegative[$alternativeId];
$denominator = $dPlus + $dMinus;
$preferences[$alternativeId] =
$denominator > 0
? $dMinus / $denominator
: 0.0;
}
return $preferences;
}
}
Urutan Perhitungan
Service di atas otomatis menjalankan:
1. Normalisasi bobot
2. Normalisasi matriks
3. Normalisasi terbobot
4. Solusi ideal positif
5. Solusi ideal negatif
6. Jarak D+
7. Jarak D-
8. Nilai preferensi
Aturan penting:
benefit:
A+ = maksimum
A- = minimum
cost:
A+ = minimum
A- = maksimum
Nilai preferensi terbesar mendapat ranking tertinggi.
Data Detail yang Dihasilkan Service
TopsisService mengembalikan data berikut:
weights
divisors
normalized
weighted
idealPositive
idealNegative
distancePositive
distanceNegative
preferences
Data tersebut dipakai pada halaman hasil untuk menampilkan proses TOPSIS secara lengkap.
Menyiapkan Matriks dari Database
Controller hasil nantinya mengambil data:
$kriteria = Kriteria::orderBy('kode')->get();
$alternatif = Alternatif::orderBy('kode')->get();
$penilaian = Penilaian::all();
Kemudian membentuk:
$matrix = [];
$weights = [];
$attributes = [];
foreach ($kriteria as $itemKriteria) {
$weights[$itemKriteria->id] =
(float) $itemKriteria->bobot;
$attributes[$itemKriteria->id] =
$itemKriteria->atribut;
}
$nilaiMap = $penilaian->keyBy(function ($item) {
return $item->alternatif_id
. '-'
. $item->kriteria_id;
});
foreach ($alternatif as $itemAlternatif) {
foreach ($kriteria as $itemKriteria) {
$key =
$itemAlternatif->id
. '-'
. $itemKriteria->id;
$matrix[$itemAlternatif->id][$itemKriteria->id] =
(float) $nilaiMap[$key]->nilai;
}
}
Format akhirnya:
$matrix = [
1 => [
1 => 80,
2 => 8,
3 => 9,
],
2 => [
1 => 70,
2 => 9,
3 => 8,
],
3 => [
1 => 75,
2 => 7,
3 => 9,
],
];
Memanggil Service
$hasil = $topsis->calculate(
$matrix,
$weights,
$attributes
);
Hasil:
[
'weights' => ...,
'divisors' => ...,
'normalized' => ...,
'weighted' => ...,
'idealPositive' => ...,
'idealNegative' => ...,
'distancePositive' => ...,
'distanceNegative' => ...,
'preferences' => ...,
]
Jika Muncul Error Pembagi 0
Periksa nilai pada menu Penilaian. Satu kolom kriteria tidak boleh seluruhnya 0.
Checkpoint
Pastikan:
- bobot dinormalisasi menjadi total mendekati
1; - pembagi normalisasi dihitung per kriteria;
- matriks normalisasi terbentuk;
- matriks terbobot terbentuk;
benefitmenggunakan maksimum untuk A+;costmenggunakan minimum untuk A+;- D+ dan D- dihitung untuk setiap alternatif;
- nilai preferensi berada pada rentang 0 sampai 1;
- pembagi nol ditangani;
- kode perhitungan berada di service.




