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
:
| Level | Pemeriksaan yang ditambahkan |
|---|---|
| 0 | Pemeriksaan dasar: class, function, dan method tak dikenal; jumlah argumen yang salah; variabel yang pasti tak terdefinisi |
| 1 | Variabel yang mungkin tak terdefinisi; method dan property magic tak dikenal pada class dengan __call/__get |
| 2 | Method tak dikenal diperiksa pada semua ekspresi (bukan hanya $this); validasi PHPDoc |
| 3 | Return type; tipe yang diberikan ke property |
| 4 | Dead code dasar: instanceof yang selalu false, cabang else mati, kode tak terjangkau setelah return |
| 5 | Pemeriksaan tipe argumen yang dikirim ke method dan function |
| 6 | Melaporkan typehint yang hilang |
| 7 | Union type yang sebagian salah: method yang hanya ada di sebagian anggota union |
| 8 | Pemanggilan method dan akses property pada tipe nullable |
| 9 | Ketat terhadap mixed eksplisit: operasi yang diizinkan hanyalah meneruskannya ke mixed lain |
| 10 | Lebih 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
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:
php vendor/bin/phpstan analyse src/level-demo.php --level=0 --no-progressNote: Using configuration file /tmp/phpstan-test/phpstan.neon.
[OK] No errorsNaikkan levelnya ke 6, dan lima temuan muncul serentak:
php vendor/bin/phpstan analyse src/level-demo.php --level=6 --no-progressNote: 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 errorsLevel 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
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:
php vendor/bin/phpstan analyse src/mixed-demo.php --level=9 --no-progressNote: 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 errorInilah 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:
php vendor/bin/phpstan analyse --generate-baseline --no-progressNote: 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:
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.phpSetiap entri memuat pesan sebagai pola regex, identifier aturan, jumlah kemunculan (count), dan berkasnya. Agar baseline benar-benar dipakai, ia harus dimasukkan ke konfigurasi utama:
includes:
- phpstan-baseline.neon
parameters:
level: 5
paths:
- srcJalankan analisis sekali lagi:
php vendor/bin/phpstan analyse --no-progressNote: Using configuration file /tmp/phpstan-test/phpstan.neon.
[OK] No errorsTiga 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:
- Hanya bekukan yang lama. Baseline dibuat sekali dari kondisi saat ini; kode baru yang melanggar tetap dilaporkan karena pesannya tidak ada di baseline.
- 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.
- Kecilkan berkala. Setelah memperbaiki beberapa temuan, jalankan ulang
--generate-baselinesupaya 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:
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-progressLangkah 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.