PHPStan bukan satu-satunya static analyzer kelas dunia di ekosistem PHP. Psalm — paket Composer dengan organisasi vimeo/psalm — menawarkan pendekatan berbeda: pemeriksaan yang teliti terhadap inferensi tipe, laporan issue yang setiap barisnya tertaut ke dokumentasi, plus dua fitur khas berupa taint analysis untuk keamanan dan --alter untuk memperbaiki kode secara otomatis. Pada Chapter ini kita memasang Psalm dari nol, menginisialisasi konfigurasinya, menganalisis berkas demo, lalu membandingkan pengalamannya dengan PHPStan yang sudah kita coba pada Chapter sebelumnya.
Memasang Psalm #
Sama seperti PHPStan, Psalm didistribusikan sebagai paket Composer. Dari direktori root proyek:
composer require --dev vimeo/psalmBaris penting dari keluaran Composer:
Using version ^6.20 for vimeo/psalmPaket ini menarik lebih banyak dependensi daripada PHPStan karena Psalm ditulis sebagai proyek PHP biasa, bukan satu berkas PHAR mandiri. Binary Psalm tersedia di vendor/bin/psalm, dan dokumentasi lengkapnya berada di psalm.dev/docs
.
Inisialisasi Konfigurasi #
Psalm dikendalikan oleh berkas psalm.xml di root proyek. Alih-alih menulisnya manual, Psalm menyediakan perintah init yang memindai proyek lalu menulis konfigurasi awal:
php vendor/bin/psalm --init src 3Dua argumen di baris tersebut menyatakan direktori sumber (src) dan level ketatness awal (3). Keluarannya singkat:
Config file created successfully. Please re-run psalm.Mari kita lihat berkas psalm.xml yang dihasilkan:
<?xml version="1.0"?>
<psalm
errorLevel="3"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>Ada tiga keputusan penting dalam konfigurasi ini:
errorLevel="3": level ketatness Psalm, berskala 1 (paling longgar) sampai 8 (paling ketat) — konsep serupa level PHPStan, tetapi dengan skala dan ambang berbeda.<directory name="src" />: hanya berkas di dalamsrcyang dianalisis.<ignoreFiles>untukvendor: dependensi pihak ketiga dikecualikan karena kualitasnya bukan tanggung jawab tim Anda.
Tergantung isi proyek, perintah --init juga bisa menambahkan atribut seperti findUnusedCode="true" yang melaporkan method dan property tak terpakai. Anda bebas menyalakan atau mematikannya sesuai selera tim.
Berkas Demo dan Analisis Pertama #
Kita memakai varian demo keranjang yang sedikit berbeda dari Chapter PHPStan
— ada docblock @return yang sengaja dibuat salah untuk menunjukkan cara Psalm menagih PHPDoc. Simpan sebagai src/keranjang.php:
<?php
declare(strict_types=1);
final class Keranjang
{
/** @var array<string, int> */
private array $items = [];
public function tambah(string $nama, int $jumlah): void
{
$this->items[$nama] = $jumlah;
}
/** @return array<string, int> */
public function semua(): array
{
return $this->items;
}
/** @return string */
public function ringkasan(): string
{
return count($this->items);
}
}
$keranjang = new Keranjang();
$keranjang->tambah('Buku Tulis', 3);
$keranjang->tambah('Pensil', 'dua');
$keranjang->kosongkan();
echo $keranjang->ringkasan() . "\n";Bug yang ditanam: argumen 'dua' untuk parameter int, pemanggilan method kosongkan() yang tidak ada, dan method ringkasan() yang berjanji mengembalikan string padahal count() mengembalikan int. Jalankan analisisnya:
php vendor/bin/psalm --no-progressKeluarannya:
ERROR: PossiblyUnusedMethod - src/keranjang.php:16:21 - Cannot find any calls to method Keranjang::semua (see https://psalm.dev/087)
public function semua(): array
ERROR: InvalidReturnType - src/keranjang.php:21:17 - The declared return type 'string' for Keranjang::ringkasan is incorrect, got 'int<0, max>' (see https://psalm.dev/011)
/** @return string */
ERROR: InvalidReturnStatement - src/keranjang.php:24:16 - The inferred type 'int<0, max>' does not match the declared return type 'string' for Keranjang::ringkasan (see https://psalm.dev/128)
return count($this->items);
ERROR: InvalidArgument - src/keranjang.php:30:30 - Argument 2 of Keranjang::tambah expects int, but 'dua' provided (see https://psalm.dev/004)
$keranjang->tambah('Pensil', 'dua');
ERROR: UndefinedMethod - src/keranjang.php:31:13 - Method Keranjang::kosongkan does not exist (see https://psalm.dev/022)
$keranjang->kosongkan();
------------------------------
5 errors found
------------------------------
Psalm can automatically fix 2 of these issues.
Run Psalm again with
--alter --issues=InvalidReturnType,PossiblyUnusedMethod --dry-run
to see what it can fix.
------------------------------(Di terminal Anda, Psalm menutup laporan dengan durasi dan pemakaian memori yang bervariasi antar mesin, jadi baris tersebut tidak dikutip di sini.) Ada lima temuan, dan exit code perintah ini adalah 2 — bukan nol, artinya cocok dijadikan gerbang CI.
Membaca Kode Masalah Psalm #
Format laporan Psalm memuat empat informasi per baris: nama jenis issue, lokasi berkas:baris:kolom, deskripsi, dan tautan dokumentasi. Mari kita bedah temuan-temuannya:
InvalidArgument(baris 30): argumen keduatambah()bertipe'dua'(string literal) padahal parameter menuntutint— padananargument.typedi PHPStan.UndefinedMethod(baris 31): methodkosongkan()tidak ada — padananmethod.notFound.InvalidReturnTypedanInvalidReturnStatement(baris 21 dan 24): dua sisi dari kontradiksiringkasan()— deklarasi@return stringdinilai salah karena isi method mengembalikanint<0, max>(notasi interval Psalm: bilangan bulat tak-negatif, hasilcount()). PHPStan melaporkan kontradiksi seperti ini sebagai satu temuan; Psalm memecahnya menjadi dua agar lokasi deklarasi dan lokasi pernyataan sama jelas.PossiblyUnusedMethod(baris 16): methodsemua()tidak pernah dipanggil di mana pun — jenis temuan kebersihan kode yang hanya muncul pada level tertentu.
Setiap temuan diakhiri tautan seperti https://psalm.dev/004 yang membuka halaman dokumentasi issue: penjelasan mengapa hal ini dianggap salah, contoh kode, dan cara memperbaikinya. Bagian akhir keluaran bahkan menawarkan perbaikan otomatis — coba dengan --dry-run terlebih dahulu untuk melihat diff tanpa mengubah berkas:
php vendor/bin/psalm --alter --issues=InvalidReturnType,PossiblyUnusedMethod --dry-runTanpa --dry-run, Psalm (yang dijuluki Psalter pada mode ini) menulis ulang berkas secara langsung — selalu review hasilnya dengan git sebelum menerima.
Psalm vs PHPStan #
Kedua analyzer membaca kontrak tipe yang sama — type declaration native dan PHPDoc — sehingga sebagian besar temuan di atas juga ditemukan PHPStan. Perbedaannya ada pada rasa pengalaman:
| Aspek | PHPStan | Psalm |
|---|---|---|
| Pemasangan | composer require --dev phpstan/phpstan | composer require --dev vimeo/psalm |
| Konfigurasi | phpstan.neon (format NEON) | psalm.xml (format XML) |
| Skala ketatness | level 0-10 | errorLevel 1-8 |
| Exit code saat ada error | 1 | 2 |
| Bentuk laporan | tabel + identifier aturan | daftar issue + tautan dokumentasi |
| Fitur khas | baseline bawaan, ekstensi komunitas | taint analysis, --alter |
Beberapa catatan untuk memilih. PHPStan berjalan sebagai PHAR mandiri dengan dependensi minimal dan menawarkan tangga level 0-10 yang sangat granular untuk adopsi bertahap. Psalm unggul di kedalaman dokumentasi issue yang menyatu dengan laporan, deteksi kode tak terpakai, serta taint analysis — mode analisis aliran data yang menelusuri input tak tepercaya (misalnya $_GET) hingga titik berbahaya seperti echo HTML atau query SQL. Tim yang serius dengan keamanan kerap menjalankan keduanya sekaligus; keduanya gratis, dan PHPDoc yang sudah rapi akan langsung dinikmati keduanya.
Ringkasan #
Kita menyelesaikan siklus lengkap Psalm: composer require --dev vimeo/psalm, inisialisasi psalm.xml lewat psalm --init src 3, lalu vendor/bin/psalm --no-progress yang menemukan lima issue — argumen salah tipe, method hantu, kontradiksi return type yang dipecah dua, hingga method tak terpakai. Fitur --alter memperbaiki sebagian temuan secara otomatis, dan exit code 2 siap menjadi gerbang CI. Dua analyzer kini ada di tangan Anda; pertanyaan berikutnya adalah bagaimana menaikkan ketatness secara bertahap tanpa tenggelam — jawabannya adalah baseline, topik Chapter berikutnya
.