Type declaration native yang kita bangun sepanjang section ini memiliki batas kosakata yang jelas. Sintaks array tidak bisa menyatakan apa isi array tersebut, callable tidak bisa menyatakan parameter dan nilai kembali yang diharapkan, dan tidak ada cara native untuk mendeskripsikan struktur array asosiatif. PHPDoc menutup celah tersebut lewat anotasi berformat standar yang ditulis di dalam docblock. Pada Chapter ini kita akan mempelajari anotasi inti @param, @return, @var, @property, dan @method, lalu menyusun tipe kaya seperti array<int, string>, class-string, dan array shape yang dipahami oleh IDE maupun static analyzer.
Mengapa Masih Perlu PHPDoc #
PHPDoc adalah konvensi anotasi standar industri yang ditulis sebagai blok komentar /** ... */ tepat sebelum class, function, method, atau property. Runtime PHP sama sekali tidak mengeksekusi isi docblock — docblock adalah komentar biasa bagi interpreter. Nilainya justru ada di alat lain yang membacanya: IDE memakainya untuk autocompletion dan peringatan, sedangkan PHPStan dan Psalm memakainya sebagai sumber kebenaran tipe ketika type declaration native tidak cukup ekspresif.
Perhatikan tiga kelemahan kosakata native berikut:
arrayhanya menyatakan “ini array”, tanpa kunci dan tipe isinya.callablehanya menyatakan “ini bisa dipanggil”, tanpa signature parameter dan return.stringuntuk nama class hanya string biasa, padahal maknanya jauh lebih spesifik.
Anotasi PHPDoc berikut menjadi kosakata pengganti ketiga celah tersebut:
| Anotasi | Target | Fungsi |
|---|---|---|
@param T $nama deskripsi | function/method | tipe sebuah parameter |
@return T deskripsi | function/method | tipe nilai kembali |
@var T $nama deskripsi | property/variabel | tipe property atau variabel lokal |
@property T $nama | class | property magic hasil __get/__set |
@property-read T $nama | class | property magic yang hanya bisa dibaca |
@method T nama(T $arg) | class | method magic hasil __call |
@template T | class/method | parameter tipe generik |
Anotasi @template beserta generics sudah kita bahas tuntas pada Chapter Generics dengan PHPDoc
, jadi Chapter ini berfokus pada lima anotasi lainnya dan sintaks tipenya.
Anotasi Inti @param, @return, dan @var #
Anotasi @param dan @return melengkapi signature function dengan informasi yang tidak muat di native type. Mari kita lihat contoh function yang menerima dua array asosiatif:
<?php
/**
* Menghitung total harga belanja.
*
* @param array<string, int> $keranjang pasangan nama barang => jumlah
* @param array<string, int> $hargaSatuan pasangan nama barang => harga satuan
* @return int total harga dalam rupiah
*/
function hitungTotal(array $keranjang, array $hargaSatuan): int
{
$total = 0;
foreach ($keranjang as $nama => $jumlah) {
$total += $hargaSatuan[$nama] * $jumlah;
}
return $total;
}
$total = hitungTotal(
['Buku Tulis' => 3, 'Pensil' => 5],
['Buku Tulis' => 4500, 'Pensil' => 2000]
);
echo "Total belanja: Rp$total\n";Output yang Dihasilkan:
Total belanja: Rp23500Mari kita baca anotasi pada docblock di atas:
@param array<string, int> $keranjang: parameter pertama adalah array dengan kuncistring(nama barang) dan nilaiint(jumlah).@param array<string, int> $hargaSatuan: parameter kedua memiliki bentuk yang sama, tetapi nilainya berarti harga satuan.@return int: nilai kembali adalah integer rupiah — redundan dengan native: int, tetapi tetap berguna sebagai tempat deskripsi.
Berbekal dua anotasi itu, PHPStan tahu bahwa $hargaSatuan[$nama] * $jumlah adalah int * int, dan IDE dapat melengkapi nama barang saat kita mengetik. Signature native array tetap menjadi penjaga runtime, sedangkan docblock menjadi peta isi bagi analyzer.
Anotasi @var memiliki dua peran. Untuk property, @var menyatakan tipe property — terutama untuk property mixed yang tipe aslinya lebih spesifik. Untuk variabel lokal, @var membantu saat tipe hasil ekspresi tidak bisa disimpulkan:
<?php
/** @var array<int, string> $daftarTugas */
$daftarTugas = ['Belajar PHPDoc', 'Menjalankan PHPStan', 'Menulis baseline'];
echo 'Tugas pertama: ', $daftarTugas[0], "\n";
echo 'Jumlah tugas: ', count($daftarTugas), "\n";Output yang Dihasilkan:
Tugas pertama: Belajar PHPDoc
Jumlah tugas: 3Anotasi @var pada baris sebelum penugasan memberi tahu analyzer bahwa $daftarTugas adalah daftar string berindeks integer. Pola @var untuk variabel lokal paling sering dipakai tepat setelah preg_match(), json_decode(), atau pengambilan data dari array besar yang bentuknya sudah diketahui penulis kode.
Sintaks Tipe di Luar Kemampuan Native #
Kekuatan sebenarnya PHPDoc ada pada kosakata tipenya. Tabel berikut merangkum bentuk-bentuk yang paling sering dipakai, lengkap dengan padanan nativenya:
| Sintaks PHPDoc | Arti | Padanan native |
|---|---|---|
array<int, string> | array berkunci int, berisi string | array |
list<string> | array berurutan (kunci 0, 1, 2, …) berisi string | array |
array{ok: bool, data: T} | array shape: kunci dan tipe per field tetap | array |
T|null atau ?T | nilai bertipe T atau null | ?T |
class-string | string berisi nama class yang valid | string |
class-string<T> | string berisi nama class turunan T | string |
callable(int): string | callable dengan satu parameter int, mengembalikan string | callable |
iterable<T> | array<T> atau Traversable<T> | iterable |
Tipe class-string layak mendapat contoh karena sering muncul di factory dan container:
<?php
/**
* Membuat instance baru dari nama class.
*
* @param class-string $namaClass nama class yang bisa di-instantiasi
*/
function buatInstance(string $namaClass): object
{
return new $namaClass();
}
$tanggal = buatInstance(DateTimeImmutable::class);
echo get_class($tanggal), "\n";Output yang Dihasilkan:
DateTimeImmutableBagi runtime, $namaClass hanyalah string — operator new menerima string apa pun yang berupa nama class. Bagi analyzer, class-string jauh lebih ketat: jika kita memanggil buatInstance('Budi'), PHPStan menolaknya karena 'Budi' tidak bisa dibuktikan sebagai nama class. Varian class-string<T> mempersempit lebih lagi ke class tertentu, misalnya class-string<Pesanan> hanya menerima Pesanan::class atau nama class turunannya.
Array Shape untuk Data Terstruktur #
Array asosiatif dengan kunci tetap adalah pola data yang sangat umum: hasil query database, payload API, baris CSV. Native hanya bisa menulis array; PHPDoc dapat menuliskan array shape — daftar field beserta tipenya:
<?php
/**
* Mengambil data produk dari katalog.
*
* @return array{nama: string, harga: int, stok: int}
*/
function cariProduk(string $kode): array
{
return [
'nama' => 'Buku Tulis Sidu',
'harga' => 4500,
'stok' => 12,
];
}
$produk = cariProduk('B001');
echo "{$produk['nama']}: Rp{$produk['harga']} (stok {$produk['stok']})\n";Output yang Dihasilkan:
Buku Tulis Sidu: Rp4500 (stok 12)Shape array{nama: string, harga: int, stok: int} menyatakan tiga hal sekaligus kepada analyzer dan IDE: field apa saja yang ada, tipe tiap field, dan bahwa tidak ada field lain. Akibatnya, $produk['hrga'] (salah ketik) langsung ditandai sebagai field yang tidak ada dalam shape, dan $produk['harga'] + 1 diyakini aman karena bertipe int. Pada runtime, nilai yang dikembalikan tetap array biasa — tidak ada validasi otomatis; shape hanyalah kontrak di atas kertas (yang ditagih oleh analyzer).
@property dan @method untuk Anggota Magic #
Method magic __get() dan __call() membuat property dan method tidak terlihat oleh IDE maupun analyzer, karena keduanya tidak benar-benar dideklarasikan. PHPDoc pada level class dapat menyatakan kontrak anggota magic tersebut:
<?php
/**
* @property-read int $saldo saldo rekening dalam rupiah
* @method int tarik(int $jumlah) mengurangi saldo dan mengembalikan sisa
*/
class Rekening
{
public function __get(string $nama): mixed
{
return match ($nama) {
'saldo' => 250000,
default => null,
};
}
public function __call(string $nama, array $argumen): mixed
{
return match ($nama) {
'tarik' => 250000 - $argumen[0],
default => null,
};
}
}
$rekening = new Rekening();
echo 'Saldo: ', $rekening->saldo, "\n";
echo 'Setelah tarik: ', $rekening->tarik(50000), "\n";Output yang Dihasilkan:
Saldo: 250000
Setelah tarik: 200000Mari kita bedah docblock class di atas:
@property-read int $saldo: class memiliki property magic hanya-baca bernama$saldobertipeint— akhiran-readkarena__get()hanya menyediakan pembacaan.@method int tarik(int $jumlah): class memiliki method magictarik()yang menerimaintdan mengembalikanint— kontrak yang dipenuhi implementasi__call()pada baris di bawahnya.
Setelah dua anotasi ini terpasang, IDE melengkapi $rekening->ta... menjadi tarik(), dan analyzer memeriksa bahwa $rekening->tarik('lima puluh') melanggar kontrak int $jumlah. Tanpa anotasi, keduanya buta terhadap anggota magic.
Kontrak Tanpa Penegak di Runtime #
Sifat PHPDoc sebagai komentar memiliki konsekuensi penting: runtime tidak pernah memvalidasi isinya, bahkan ketika isinya jelas-jelas salah. Perhatikan docblock berikut yang berbohong total:
<?php
/**
* @param Gajah $hewan tipe yang tidak ada sekalipun tetap diabaikan
* @return string[] klaim palsu: sebenarnya mengembalikan string
*/
function suara($hewan)
{
return 'pret';
}
echo suara('kuda'), "\n";Output yang Dihasilkan:
pretProgram berjalan mulus dan mencetak pret, meskipun tipe Gajah tidak pernah didefinisikan dan nilai kembali bertentangan dengan klaim string[]. Interpreter hanya melihat function suara() tanpa parameter bertipe dan return 'pret' — docblock dilewati begitu saja. Inilah sebabnya PHPDoc tidak pernah menggantikan type declaration native: native dijaga runtime, PHPDoc dijaga alat pihak luar.
Ringkasan #
PHPDoc memperluas kosakata tipe jauh melampaui kemampuan native: @param dan @return memetakan isi array, @var menandai property dan variabel lokal, @property dan @method mendokumentasikan anggota magic, sementara sintaks seperti array<int, string>, list<T>, class-string<T>, dan array shape memberi tipe yang presisi. Semua anotasi ini hidup sebagai kontrak di atas kertas — runtime mengabaikannya sepenuhnya. Kontrak baru bermakna jika ada yang menegakkannya, dan itulah peran static analysis yang akan kita selami pada Chapter berikutnya.