Pada Chapter mutation testing kita menguji kualitas suite test dengan menyuntikkan bug dan memastikan test menjadi merah. Static analysis bekerja dari arah sebaliknya: sebuah alat membaca kode tanpa menjalankan satu baris pun, lalu melaporkan simbol yang tidak dikenal, jumlah argumen yang tidak cocok, dan pelanggaran kontrak tipe — seluruhnya kelas bug yang idealnya tidak pernah sempat sampai ke tahap test. Teori di baliknya sudah kita bahas tuntas pada Section Type System dan Static Analysis, mulai dari Chapter static analysis hingga menggunakan PHPStan . Chapter ini mengambil sudut pandang pipeline: di mana static analysis ditempatkan, mengapa ia berjalan sebelum test, dan bagaimana menjaga disiplinnya supaya laporan alat ini tetap dipercaya seluruh tim.
Bug yang Menunggu Dijalankan #
Untuk merasakan mengapa static analysis layak mendapat tahap tersendiri di pipeline, mari kita saksakan empat bug nyata yang baru terasa ketika interpreter mengeksekusi barisnya. Keempat contoh berikut dapat dijalankan langsung, dan keempatnya juga akan dilaporkan PHPStan dalam hitungan detik tanpa mengeksekusi kode sama sekali. Bug pertama adalah salah ketik nama method:
<?php
$waktu = new DateTimeImmutable(
"2026-01-15 08:00:00",
new DateTimeZone("Asia/Jakarta"),
);
echo $waktu->formatt("Y-m-d") . "\n";Output yang Dihasilkan:
Fatal error: Uncaught Error: Call to undefined method DateTimeImmutable::formatt() in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php:8
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php on line 8Mari kita bedah kegagalannya baris per baris:
- Baris 3-6: objek
DateTimeImmutabledibuat untuk waktu tetap di zona Asia/Jakarta; sampai di sini semuanya sah. - Baris 8: kode memanggil method
formatt()— padahal method yang benar bernamaformat(). Interpreter baru menyadari method itu tidak ada ketika baris ini dieksekusi, sehinggaErrorfatal menghentikan skrip dengan exit code 255.
PHPStan tidak perlu menunggu baris kedelapan dieksekusi. Alat tersebut memuat definisi DateTimeImmutable melalui refleksi, membaca daftar method-nya, dan langsung melaporkan bahwa formatt() tidak ada. Nama berkas dan nomor baris pada pesan galat di atas menyesuaikan lokasi berkas di komputer Anda.
Bug kedua adalah jumlah argumen yang tidak cocok dengan signature fungsi:
<?php
$namaDepan = "Budi";
$namaBelakang = "Santoso";
echo strlen($namaDepan, $namaBelakang) . "\n";Output yang Dihasilkan:
Fatal error: Uncaught ArgumentCountError: strlen() expects exactly 1 argument, 2 given in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php:6
Stack trace:
#0 /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php(6): strlen('Budi', 'Santoso')
#1 {main}
thrown in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php on line 6Fungsi internal strlen() menerima tepat satu parameter, tetapi pemanggilan di baris 6 mengirim dua. PHP menolak pemanggilan tersebut dengan ArgumentCountError — sekali lagi, baru ketika barisnya dieksekusi. PHPStan mengenal signature seluruh fungsi internal PHP dari refleksi bawaan, sehingga pemanggilan seperti ini ditandai sejak kode dibaca, jauh sebelum ada yang menjalankannya.
Bug ketiga adalah pelanggaran kontrak tipe di bawah declare(strict_types=1):
<?php
declare(strict_types=1);
function potongHarga(int $harga): int
{
return $harga - 5000;
}
$totalBelanja = "150000";
echo potongHarga($totalBelanja) . "\n";Output yang Dihasilkan:
Fatal error: Uncaught TypeError: potongHarga(): Argument #1 ($harga) must be of type int, string given, called in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php on line 12 and defined in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php:5
Stack trace:
#0 /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php(12): potongHarga('150000')
#1 {main}
thrown in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php on line 5Pada mode strict, PHP menolak mengubah string "150000" menjadi int secara otomatis, sehingga pemanggilan potongHarga($totalBelanja) melempar TypeError. PHPStan menangkap pola ini tanpa menjalankan kode: dari literal "150000", alat tersebut menyimpulkan tipe variabel $totalBelanja sebagai string, lalu membandingkannya dengan deklarasi parameter int $harga dan melaporkan ketidakcocokannya.
Bug keempat adalah yang paling licik karena tidak ada yang berhenti:
<?php
$totalDiskon = 15000;
echo "Diskon: $totalDiskonn\n";Output yang Dihasilkan:
Warning: Undefined variable $totalDiskonn in /home/z/my-project/php-course/content/php/testing-dan-quality-assurance/static-analysis/.verify_tmp.php on line 5
Diskon:Huruf n yang nyasar pada $totalDiskonn hanya memicu Warning, lalu program berjalan terus dan mencetak Diskon: tanpa angka. Tidak ada yang crash, tidak ada test yang otomatis gagal — bug seperti ini bisa hidup berhari-hari di produksi. PHPStan menganggap variabel yang mungkin tidak terdefinisi sebagai error, bukan gangguan ringan yang boleh diabaikan.
Membaca Tanpa Menjalankan #
Cara kerja static analysis dapat diringkas dalam empat langkah: alat mem-parsing kode menjadi struktur pohon, membangun tabel simbol (fungsi, class, method, dan variabel apa saja yang ada), menyimpulkan tipe setiap ekspresi dari deklarasi dan pemakaiannya, lalu membandingkan semua itu dengan kontrak yang tertulis di signature. Seluruh proses berlangsung tanpa satu pun baris kode dieksekusi. Untuk keempat bug di atas, rangkuman laporan PHPStan kira-kira berbunyi:
Call to an undefined method DateTimeImmutable::formatt().
Function strlen() invoked with 2 parameters, 1 required.
Parameter #1 $harga of function potongHarga expects int, string given.
Variable $totalDiskonn might not be defined.Perintah untuk menjalankan analisis pada proyek nyata singkatnya seperti ini:
vendor/bin/phpstan analyse src tests --level=8 --no-progressFlag --no-progress membuat keluaran bersahabat dengan log CI. Panduan lengkap instalasi, berkas phpstan.neon, dan arti tiap analysis level dibahas pada Chapter menggunakan PHPStan
; Psalm sebagai alternatif dibahas pada Chapter menggunakan Psalm
di section yang sama.
Tahap Pertama di Pipeline #
Dalam pipeline QA, static analysis ditempatkan sebelum test suite — dan urutan itu bukan kebetulan. Analisis selesai dalam hitungan detik tanpa database, tanpa fixture, tanpa persiapan apa pun selain kode itu sendiri; sementara test suite yang membangun skema database dan menjalankan ratusan kasus bisa memakan menit. Menutup kelas bug yang murah lebih dulu berarti suite test yang mahal hanya berurusan dengan bug yang benar-benar membutuhkan eksekusi. Berikut wujud tahap static analysis sebagai job GitHub Actions:
name: qa
on:
pull_request:
push:
branches: [main]
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
- uses: ramsey/composer-install@v3
- name: PHPStan
run: vendor/bin/phpstan analyse --no-progressMari kita baca langkah-langkahnya:
actions/checkout@v4menaruh salinan kode pada runner.shivammathur/setup-php@v2memasang PHP 8.4 — action standar de facto ekosistem PHP untuk CI.ramsey/composer-install@v3menjalankancomposer installsekaligus menyimpan cache unduhan dependensi yang dikunci pada isicomposer.lock; eksekusi berikutnya memakai cache dan jauh lebih cepat.- Langkah
PHPStanmenjalankan analisis; setiap error yang dilaporkan membuat perintah keluar dengan exit code bukan nol, dan job otomatis menjadi merah.
Disiplin Baseline #
Memasang static analysis pada proyek yang sudah berjalan tidak pernah mulus: ribuan baris lama bisa menghasilkan ratusan laporan sekaligus. Menegakkan “nol error” pada hari pertama berarti pipeline merah selama berminggu-minggu dan tim belajar mengabaikannya — tepat kebiasaan yang ingin kita hindari. PHPStan menyediakan jalan keluar bernama baseline: berkas yang mencatat seluruh error yang sudah ada, sehingga pipeline hanya merah untuk error baru.
vendor/bin/phpstan analyse --baseline
git add phpstan-baseline.neon
git commit -m "Catat utang analisis yang ada sebagai baseline"Baseline adalah buku catatan utang, dan buku catatan utang hanya boleh menyusut. Mekanisme level dan strategi menaikkan level secara bertahap dibahas tuntas pada Chapter baseline dan analysis levels ; di sini kita cukup memegang tiga aturan mainnya:
- Baseline diregenerasi hanya di commit khusus yang direview, bukan sambil-sambil di dalam pull request fitur.
- Baseline tidak pernah diregenerasi demi menghidupkan pipeline yang merah — itu sama dengan mematikan alarm kebakaran.
- Setiap perbaikan kode lama harus menghapus entrinya dari baseline; baseline yang membesar dari waktu ke waktu adalah tanda bahaya.
Pre-commit Hook #
Pipeline memberi umpan balik dalam hitungan menit; pre-commit hook memberikannya sebelum commit itu sendiri terbentuk. Hook adalah skrip kecil yang dijalankan Git pada momen tertentu — kali ini tepat sebelum sebuah commit diterima. Simpan skrip berikut sebagai .git/hooks/pre-commit lalu beri hak eksekusi dengan chmod +x:
#!/bin/sh
vendor/bin/phpstan analyse --no-progressDengan hook ini, commit dibatalkan otomatis ketika analisis menemukan error, dan salah ketik nama method tertangkap dalam hitungan detik — bukan menit kemudian di CI. Pada proyek besar, jalankan analisis hanya pada berkas yang ikut di-commit (daftarnya bisa diambil dari git diff --cached --name-only) agar hook tetap terasa instan. Satu catatan penting: hook di .git/hooks bersifat lokal dan tidak ikut tersimpan dalam repositori; untuk membagikannya ke seluruh tim, simpan di folder proyek seperti .githooks/ lalu aktifkan dengan git config core.hooksPath .githooks.
Saling Melengkapi dengan Test #
Static analysis dan test bukan pesaing yang saling menggantikan; keduanya menjaga sisi yang berbeda dari kode yang sama. Tabel berikut memetakan pembagian kerjanya:
| Aspek | Static analysis | Test otomatis |
|---|---|---|
| Cara menemukan bug | membaca kode tanpa eksekusi | menjalankan kode dan mengamati hasil |
| Momen penemuan | sebelum kode dieksekusi | saat suite test berjalan |
| Kelas bug khas | simbol tak dikenal, argumen salah, pelanggaran tipe | logika bisnis salah, hasil meleset dari harapan |
| Pertanyaan bisnis (“apakah diskon member memang 10%?”) | tidak dapat dijawab | dijawab lewat assertion nilai harapan |
| Kebutuhan eksekusi | tidak ada | database, fixture, atau dependensi lain |
| Durasi tahap di pipeline | hitungan detik | detik hingga menit |
PHPStan tidak akan pernah tahu bahwa diskon member seharusnya 10% — pengetahuan itu tidak tertulis di signature, hanya ada di kepala pemilik bisnis, dan satu-satunya tempat menyimpannya adalah assertion test. Sebaliknya, suite test tidak murah memindai setiap call site hanya untuk menghitung jumlah argumen. Kombinasi keduanya menutup celah masing-masing; itulah alasan keduanya berdiri sebagai tahap berurutan di pipeline, bukan salah satu saja.
Ringkasan #
Static analysis membaca kode tanpa menjalankannya dan menutup kelas bug — method yang tidak ada, argumen yang salah jumlah, tipe yang dilanggar, variabel yang tidak terdefinisi — sebelum test suite menyala. Di pipeline, tahap ini berjalan paling awal karena paling murah; baseline menjaga adopsi di proyek lama tetap manusiawi selama utangnya hanya boleh menyusut; pre-commit hook memendekkan umpan balik hingga hitungan detik. Setelah kontrak tipe terjaga, satu kelompok masalah masih tersisa di pipeline: gaya penulisan kode yang berbeda-beda antar anggota tim. Chapter berikutnya membahas coding standards — tahap lint yang menjaga keseragaman itu secara otomatis.
Referensi #
- Memulai PHPStan: https://phpstan.org/user-guide/getting-started
- Penanganan error PHP: https://www.php.net/manual/en/language.error-handling.php
- Psalm, alternatif static analysis: https://psalm.dev/docs/