Topik ini membahas attribute, yaitu anotasi terstruktur yang ditulis pada deklarasi kode dan dibaca kembali pada saat program berjalan. Pembahasan mencakup cara attribute dibentuk, cara membuat attribute sendiri dengan kelas khusus, dan cara membacanya melalui reflection, yaitu API untuk mengintip struktur kelas pada waktu eksekusi. Contoh memakai pemetaan rute pada panel arsip. Setelah membacanya, keterangan yang sebelumnya ditulis sebagai komentar bebas dapat diubah menjadi data terstruktur yang dapat diperiksa program.
Attribute sebagai metadata#
Attribute dipasang pada deklarasi kelas, metode, fungsi, properti, parameter, maupun konstanta kelas dengan menuliskan #[Nama] tepat di atas deklarasinya. Attribute tidak melakukan apa pun dengan sendirinya, melainkan menyimpan keterangan di dalam struktur kode, dan keterangan itu baru berperan ketika ada bagian program yang membacanya. Konvensi keterangan berbasis komentar, seperti @Route pada docblock, tidak pernah diperiksa bahasa, sedangkan attribute dikenali parser PHP sehingga salah tulisnya terlihat sejak proses pemuatan.
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
class Rute
{
public function __construct(
public readonly string $jalur,
public readonly string $metode = 'GET',
) {
}
}
class PanelArsip
{
#[Rute('/arsip/daftar')]
public function daftar(): string
{
return 'Menampilkan daftar arsip';
}
#[Rute('/arsip/simpan', 'POST')]
public function simpan(): string
{
return 'Menyimpan berkas ke arsip';
}
public function bantuan(): string
{
return 'Halaman bantuan';
}
}
$refleksi = new ReflectionClass(PanelArsip::class);
foreach ($refleksi->getMethods() as $metode) {
$atribut = $metode->getAttributes(Rute::class);
if ($atribut === []) {
continue;
}
$rute = $atribut[0]->newInstance();
printf("%s %s -> %s\n", $rute->metode, $rute->jalur, $metode->getName());
}GET /arsip/daftar -> daftar
POST /arsip/simpan -> simpanPHP juga menyediakan attribute bawaan. #[Override] sejak PHP 8.3 memastikan metode benar-benar menimpa metode milik kelas induk, dan #[\Deprecated] sejak PHP 8.4 menandai fungsi yang telah ditinggalkan sehingga pemanggilannya memunculkan pemberitahuan. Attribute bawaan dievaluasi oleh PHP sendiri, sedangkan attribute buatan pengguna baru berperan setelah dibaca lewat reflection.
Membuat attribute sendiri#
Kelas yang dipakai sebagai attribute dinyatakan dengan menuliskan #[Attribute(...)] di atas deklarasinya, tanpa kewajiban mewarisi kelas tertentu. Argumen pada konstruktornya menjadi argumen yang ditulis pemakai, dan tipe parameter konstruktornya menentukan apa saja yang boleh dituliskan pada pemakaian. Penanda Attribute::TARGET_METHOD pada contoh di atas membatasi attribute Rute agar hanya boleh dipasang pada metode, dan konstanta lain seperti TARGET_CLASS serta TARGET_PROPERTY menyediakan batasan yang setara untuk sasaran lain. Beberapa sasaran dapat digabung dengan operator bitwise |.
Pada contoh sebelumnya, #[Rute('/arsip/simpan', 'POST')] membuat objek Rute dengan properti $jalur bernilai /arsip/simpan dan $metode bernilai POST, sedangkan pemakaian dengan satu argumen jatuh ke nilai bawaan GET. Propertinya dideklarasikan readonly karena metadata rute tidak dirancang untuk diubah setelah dibentuk. Pola ini membuat kelas attribute hampir selalu berbentuk kelas kecil dengan konstruktor promosi dan tanpa perilaku tambahan.
Membaca attribute dengan reflection#
Kelas ReflectionClass dan ReflectionMethod menyediakan method getAttributes(), yaitu pintu masuk membaca attribute. Method ini mengembalikan array berisi objek ReflectionAttribute, dan objek itu belum berupa instance attribute, melainkan keterangan tentang attribute yang terpasang. Pemanggilan newInstance() barulah membuat instance attribute dengan memanggil konstruktornya memakai argumen yang tertulis pada kode.
Pada contoh di atas, perulangan mengambil seluruh metode kelas PanelArsip, menyaring hanya yang membawa attribute Rute, lalu mencetak satu baris tabel rute untuk setiap kecocokan. Method bantuan() tidak muncul pada keluaran karena tidak membawa attribute. Argumen pertama getAttributes() berfungsi sebagai filter nama kelas. Penambahan bendera ReflectionAttribute::IS_INSTANCEOF memperluas filter tersebut, sehingga attribute turunan dari kelas yang disebut juga ikut terambil. Sifat ini berguna ketika attribute diberi hierarki.
Attribute yang terpasang pada kelas dibaca dengan method yang sama milik ReflectionClass, sehingga satu tahap pembacaan dapat menggabungkan metadata tingkat kelas dan tingkat metode. Pola yang lazim adalah menaruh keterangan umum, misalnya nama panel atau pembatasan akses, pada kelas, dan keterangan per aksi pada metode, lalu menyatukan keduanya ketika peta rute disusun. Bendera IS_INSTANCEOF berlaku pada kedua tingkat tersebut dengan perilaku yang sama, dan satu deklarasi boleh memuat beberapa attribute sekaligus yang semuanya terambil dalam urutan penulisannya.
Kesalahan umum#
Dua pelanggaran yang paling sering muncul sama-sama baru terasa pada pemanggilan newInstance(). Pertama, attribute yang dipasang pada sasaran yang salah, misalnya Rute yang menargetkan metode tetapi dipasang pada kelas. Kedua, kelas yang dipakai sebagai attribute tanpa deklarasi #[Attribute]. Contoh berikut menampilkan keduanya dengan penangkapan Error.
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
class Rute
{
}
#[Rute]
class PanelArsip
{
}
class BukanAtribut
{
}
#[BukanAtribut]
class PanelBantuan
{
}
$panel = new ReflectionClass(PanelArsip::class);
$panelLain = new ReflectionClass(PanelBantuan::class);
try {
$panel->getAttributes()[0]->newInstance();
} catch (Error $e) {
echo 'Error: ', $e->getMessage(), PHP_EOL;
}
try {
$panelLain->getAttributes()[0]->newInstance();
} catch (Error $e) {
echo 'Error: ', $e->getMessage(), PHP_EOL;
}Error: Attribute "Rute" cannot target class (allowed targets: method)
Error: Attempting to use non-attribute class "BukanAtribut" as attributeKesalahan ketiga adalah mengira attribute dievaluasi saat dipasang. Pemeriksaan sasaran dan pemeriksaan kelas attribute memang berjalan pada waktu kompilasi, tetapi pembuatan instance-nya sepenuhnya milik kode pembaca. Oleh karena itu, kerangka kerja yang memakai attribute untuk konfigurasi menyatukan pembacaannya di satu tempat agar kesalahan pemakaian dilaporkan secara seragam, bukan tersebar pada titik pemanggilan acak.
Ringkasan#
- Attribute adalah metadata terstruktur yang dikenali parser PHP dan terpasang pada deklarasi kelas, metode, fungsi, properti, serta parameter.
- Kelas attribute dinyatakan dengan
#[Attribute(...)]dan batasan sasarannya ditetapkan lewat konstantaTARGET_*. - Reflection membaca attribute melalui
getAttributes(), dannewInstance()barulah membuat instance attribute dengan argumen yang tertulis. - Pemasangan pada sasaran yang salah atau pemakaian kelas tanpa deklarasi
#[Attribute]melempar Error pada saat instance dibuat.
Referensi#
- Attributes — PHP Manual — Dokumentasi resmi attribute beserta deklarasi kelas attribute dan batasan sasarannya.
- Declaring and Reading Attribute Values — PHP Manual
— Dokumentasi pembacaan attribute dengan reflection dan
newInstance(). - ReflectionClass — PHP Manual
— Dokumentasi kelas reflection yang menyediakan
getAttributes()dangetMethods(). - Fitur baru PHP 8.0 — PHP Manual — Daftar fitur PHP 8.0 yang memperkenalkan attributes.