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:

bash
composer require --dev vimeo/psalm

Baris penting dari keluaran Composer:

text
Using version ^6.20 for vimeo/psalm

Paket 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:

bash
php vendor/bin/psalm --init src 3

Dua argumen di baris tersebut menyatakan direktori sumber (src) dan level ketatness awal (3). Keluarannya singkat:

text
Config file created successfully. Please re-run psalm.

Mari kita lihat berkas psalm.xml yang dihasilkan:

xml
<?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:

  1. errorLevel="3": level ketatness Psalm, berskala 1 (paling longgar) sampai 8 (paling ketat) — konsep serupa level PHPStan, tetapi dengan skala dan ambang berbeda.
  2. <directory name="src" />: hanya berkas di dalam src yang dianalisis.
  3. <ignoreFiles> untuk vendor: 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
<?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:

bash
php vendor/bin/psalm --no-progress

Keluarannya:

text
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:

  1. InvalidArgument (baris 30): argumen kedua tambah() bertipe 'dua' (string literal) padahal parameter menuntut int — padanan argument.type di PHPStan.
  2. UndefinedMethod (baris 31): method kosongkan() tidak ada — padanan method.notFound.
  3. InvalidReturnType dan InvalidReturnStatement (baris 21 dan 24): dua sisi dari kontradiksi ringkasan() — deklarasi @return string dinilai salah karena isi method mengembalikan int<0, max> (notasi interval Psalm: bilangan bulat tak-negatif, hasil count()). PHPStan melaporkan kontradiksi seperti ini sebagai satu temuan; Psalm memecahnya menjadi dua agar lokasi deklarasi dan lokasi pernyataan sama jelas.
  4. PossiblyUnusedMethod (baris 16): method semua() 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:

bash
php vendor/bin/psalm --alter --issues=InvalidReturnType,PossiblyUnusedMethod --dry-run

Tanpa --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:

AspekPHPStanPsalm
Pemasangancomposer require --dev phpstan/phpstancomposer require --dev vimeo/psalm
Konfigurasiphpstan.neon (format NEON)psalm.xml (format XML)
Skala ketatnesslevel 0-10errorLevel 1-8
Exit code saat ada error12
Bentuk laporantabel + identifier aturandaftar issue + tautan dokumentasi
Fitur khasbaseline bawaan, ekstensi komunitastaint 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 .