Menghidupkan analyzer di level paling ketat pada codebase warisan lama ibarat menyalakan lampu stadion di gudang penuh debu: ratusan temuan muncul sekaligus dan tidak seorang pun tahu harus mulai membersihkan dari mana. PHPStan menjawab masalah adopsi ini dengan dua mekanisme yang saling melengkapi: tangga level 0-10 untuk memilih seberapa ketat pemeriksaan dijalankan, dan baseline untuk membekukan temuan lama supaya hanya pelanggaran baru yang memblokir pekerjaan. Pada Chapter ini kita membedah keduanya, melanjutkan proyek demo keranjang dari Chapter PHPStan , lalu menutup dengan strategi ratchet dan penegakan di CI.

Sebelas Level Analisis #

PHPStan menyediakan sebelas level (0-10) yang bisa dipilih lewat --level pada perintah analyse atau kunci level di phpstan.neon. Level bersifat kumulatif: menjalankan level 5 berarti seluruh pemeriksaan level 0-4 ikut aktif. Level default adalah 0 — pemeriksaan paling longgar — dan --level max adalah alias untuk level tertinggi agar proyek Anda otomatis ikut naik ketika PHPStan merilis level baru. Daftar lengkap berikut merangkum apa yang ditambahkan tiap level menurut dokumentasi resmi rule levels :

LevelPemeriksaan yang ditambahkan
0Pemeriksaan dasar: class, function, dan method tak dikenal; jumlah argumen yang salah; variabel yang pasti tak terdefinisi
1Variabel yang mungkin tak terdefinisi; method dan property magic tak dikenal pada class dengan __call/__get
2Method tak dikenal diperiksa pada semua ekspresi (bukan hanya $this); validasi PHPDoc
3Return type; tipe yang diberikan ke property
4Dead code dasar: instanceof yang selalu false, cabang else mati, kode tak terjangkau setelah return
5Pemeriksaan tipe argumen yang dikirim ke method dan function
6Melaporkan typehint yang hilang
7Union type yang sebagian salah: method yang hanya ada di sebagian anggota union
8Pemanggilan method dan akses property pada tipe nullable
9Ketat terhadap mixed eksplisit: operasi yang diizinkan hanyalah meneruskannya ke mixed lain
10Lebih ketat lagi: mixed implisit (typehint yang hilang) juga dilaporkan

Menaikkan Level dalam Praktik #

Untuk merasakan lompatan antar level, kita tambahkan berkas src/level-demo.php ke proyek demo (yang masih memakai phpstan.neon dari Chapter sebelumnya):

php
<?php

declare(strict_types=1);

function hitungPajak($harga, $persen)
{
    return $harga * $persen / 100;
}

function kapitalisasi($teks)
{
    return strtoupper($teks);
}

echo hitungPajak(100000, 11), "\n";
echo kapitalisasi('siap'), "\n";

Kode ini berjalan normal — kedua function hanya kehilangan typehint. Pada level 0 semuanya dianggap bersih:

bash
php vendor/bin/phpstan analyse src/level-demo.php --level=0 --no-progress
text
Note: Using configuration file /tmp/phpstan-test/phpstan.neon.

 [OK] No errors

Naikkan levelnya ke 6, dan lima temuan muncul serentak:

bash
php vendor/bin/phpstan analyse src/level-demo.php --level=6 --no-progress
text
Note: Using configuration file /tmp/phpstan-test/phpstan.neon.
 ------ ----------------------------------------------------------------------
  Line   level-demo.php
 ------ ----------------------------------------------------------------------
  5      Function hitungPajak() has no return type specified.
         🪪  missingType.return
  5      Function hitungPajak() has parameter $harga with no type specified.
         🪪  missingType.parameter
  5      Function hitungPajak() has parameter $persen with no type specified.
         🪪  missingType.parameter
  10     Function kapitalisasi() has no return type specified.
         🪪  missingType.return
  10     Function kapitalisasi() has parameter $teks with no type specified.
         🪪  missingType.parameter
 ------ ----------------------------------------------------------------------


 [ERROR] Found 5 errors

Level 6 menagih typehint yang hilang melalui identifier missingType.parameter dan missingType.return. Sekarang ganti parameter pertanyaan dengan mixed eksplisit — berkas src/mixed-demo.php berikut hanya melanggar aturan level tinggi:

php
<?php

declare(strict_types=1);

function kapitalisasi(mixed $teks): string
{
    return strtoupper($teks);
}

echo kapitalisasi('siap'), "\n";

Pada level 0 sampai 8 berkas ini lolos, karena mixed memang deklarasi yang sah. Level 9 menilainya berbeda:

bash
php vendor/bin/phpstan analyse src/mixed-demo.php --level=9 --no-progress
text
Note: Using configuration file /tmp/phpstan-test/phpstan.neon.
 ------ -------------------------------------------------------------------
  Line   mixed-demo.php
 ------ -------------------------------------------------------------------
  7      Parameter #1 $string of function strtoupper expects string, mixed
         given.
         🪪  argument.type
 ------ -------------------------------------------------------------------


 [ERROR] Found 1 error

Inilah inti level 9: begitu sebuah nilai dinyatakan mixed, nilai itu tidak boleh menyentuh operasi apa pun — strtoupper() menuntut string, dan mixed tidak bisa dibuktikan demikian. Perbaikannya selalu dua arah: persempit tipe di sumber (misalnya string), atau sempitkan dengan pemeriksaan is_string() sebelum dipakai. Pola level bertahap ini pula yang membuat tim bisa naik tangga satu-satu: level 5 menjamin argumen konsisten, level 6 memaksa semua signature eksplisit, level 9-10 menutup keran mixed sama sekali.

Baseline: Membekukan Temuan Lama #

Kembali ke berkas keranjang.php demo kita yang masih menyimpan tiga error di level 5. Menaikkan level tanpa memperbaiki ketiganya berarti CI akan merah selamanya — di sinilah baseline masuk. PHPStan dapat mencatat seluruh temuan saat ini ke sebuah berkas yang kemudian diabaikan:

bash
php vendor/bin/phpstan analyse --generate-baseline --no-progress
text
Note: Using configuration file /tmp/phpstan-test/phpstan.neon.

 [OK] Baseline generated with 3 errors.

Perintah tersebut membuat berkas phpstan-baseline.neon berisi daftar temuan yang dibekukan:

neon
parameters:
	ignoreErrors:
		-
			message: '#^Call to an undefined method Keranjang\:\:kosongkan\(\)\.$#'
			identifier: method.notFound
			count: 1
			path: src/keranjang.php

		-
			message: '#^Parameter \#1 \$string of function strlen expects string, Keranjang given\.$#'
			identifier: argument.type
			count: 1
			path: src/keranjang.php

		-
			message: '#^Parameter \#2 \$jumlah of method Keranjang\:\:tambah\(\) expects int, string given\.$#'
			identifier: argument.type
			count: 1
			path: src/keranjang.php

Setiap entri memuat pesan sebagai pola regex, identifier aturan, jumlah kemunculan (count), dan berkasnya. Agar baseline benar-benar dipakai, ia harus dimasukkan ke konfigurasi utama:

neon
includes:
    - phpstan-baseline.neon
parameters:
    level: 5
    paths:
        - src

Jalankan analisis sekali lagi:

bash
php vendor/bin/phpstan analyse --no-progress
text
Note: Using configuration file /tmp/phpstan-test/phpstan.neon.

 [OK] No errors

Tiga error lama kini tersembunyi, dan exit code kembali 0. Daftar lengkap opsi untuk memformat, memisahkan, dan menyaring baseline didokumentasikan di panduan ignoring errors serta referensi konfigurasi . Psalm memiliki mekanisme serupa lewat berkas baseline XML yang dibuat dengan --set-baseline.

Strategi Ratchet #

Baseline yang benar dipakai seperti kunci pas (ratchet): utang lama boleh tergantung, tetapi roda tidak boleh berputar mundur. Tiga aturan mainnya:

  1. Hanya bekukan yang lama. Baseline dibuat sekali dari kondisi saat ini; kode baru yang melanggar tetap dilaporkan karena pesannya tidak ada di baseline.
  2. Jadikan ukuran baseline metrik utang. Jumlah entri yang tercatat adalah jumlah utang teknis tipe Anda — tampilkan angkanya di dashboard tim agar terlihat turun, bukan naik.
  3. Kecilkan berkala. Setelah memperbaiki beberapa temuan, jalankan ulang --generate-baseline supaya entri yang sudah lunas hilang dan baseline kembali mewakili kenyataan.

Menegakkan di CI #

Seluruh strategi di atas hanya menggigit ketika analisis berjalan otomatis di setiap perubahan. Contoh berikut memasang PHPStan sebagai gerbang di GitHub Actions:

yaml
name: static-analysis

on: [push, pull_request]

jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.4"

      - run: composer install --no-progress

      - run: vendor/bin/phpstan analyse --no-progress

Langkah terakhir memanggil analisis dengan konfigurasi proyek (termasuk baseline). Karena PHPStan mengembalikan exit code 1 saat ada temuan — dan Psalm mengembalikan 2 — langkah tersebut otomatis gagal dan pull request ditahan sampai temuan baru dibereskan. GitLab CI, CircleCI, dan layanan lain menangani exit code non-nol dengan cara yang sama, jadi polanya portabel ke mana pun tim Anda bekerja.

Ringkasan #

Tangga level 0-10 memberi kendali granular atas seberapa ketat PHPStan menilai kode — dari pemeriksaan dasar hingga larangan total atas mixed — sementara baseline membekukan temuan lama supaya adopsi bisa berjalan mundur dari tengah medan. Kombinasi level yang naik bertahap, baseline yang menyusut, dan CI yang menolak temuan baru membentuk mesin ratchet: kualitas tipe hanya boleh bergerak ke satu arah. Semua alat kini lengkap; Chapter penutup section ini merangkainya menjadi strategi membangun kode yang type-safe dari baris pertama.