Ada kalanya program perlu bertanya tentang dirinya sendiri: class apa ini, method apa saja yang dimilikinya, attribute apa yang menempel padanya? Jawaban atas pertanyaan-pertanyaan tersebut dibutuhkan oleh dependency injection container yang menyusun object otomatis, ORM yang memetakan class ke tabel database, sampai debugger yang menampilkan isi object. PHP menyediakan reflection — sekumpulan class bawaan (seperti ReflectionClass, ReflectionMethod, ReflectionProperty) yang mampu membedah struktur kode saat runtime. Pada chapter ini kita pelajari API-nya yang paling sering dipakai, membuktikan bahwa reflection bisa membaca attribute dari chapter sebelumnya, lalu merakit mini container untuk melihat kekuatannya di dunia nyata.

Memeriksa Struktur Class #

Gerbang utama reflection adalah ReflectionClass. Class ini dibangun dari nama class (paling aman lewat konstanta ::class), lalu menyediakan puluhan method untuk menelusuri anggotanya:

php
<?php

class Produk
{
    public string $nama;
    protected int $stok;
    private float $hargaModal;

    public function __construct(string $nama, int $stok, float $hargaModal)
    {
        $this->nama = $nama;
        $this->stok = $stok;
        $this->hargaModal = $hargaModal;
    }

    public function hitungHargaJual(): float
    {
        return $this->hargaModal * 1.3;
    }

    private function catatAudit(): void
    {
        // dicatat untuk keperluan internal
    }
}

$refleksi = new ReflectionClass(Produk::class);

echo "Method:\n";
foreach ($refleksi->getMethods() as $method) {
    $visibilitas = $method->isPublic() ? "public" : ($method->isProtected() ? "protected" : "private");
    echo "- {$method->getName()} ($visibilitas)\n";
}

echo "Property:\n";
foreach ($refleksi->getProperties() as $prop) {
    echo "- \${$prop->getName()}\n";
}

Output yang Dihasilkan:

text
Method:
- __construct (public)
- hitungHargaJual (public)
- catatAudit (private)
Property:
- $nama
- $stok
- $hargaModal

Mari kita bedah bagian pentingnya:

  1. Baris 27 (new ReflectionClass(Produk::class)): kita membuat object refleksi untuk class Produk. Dari sini, seluruh struktur Produk bisa dijelajahi tanpa perlu instance-nya.
  2. getMethods(): mengembalikan array ReflectionMethod untuk semua method — termasuk __construct dan method private seperti catatAudit(). Urutannya mengikuti urutan deklarasi.
  3. isPublic(), isProtected(): setiap ReflectionMethod juga melaporkan sifatnya; di sini kita memakainya untuk merangkai label visibility.
  4. getProperties(): mengembalikan array ReflectionProperty untuk nama, stok, dan hargaModal — visibility tidak menjadi penghalang untuk mengetahui bahwa property itu ada.

Selain dua method di atas, ReflectionClass masih menyediakan getConstants() untuk konstanta, getParentClass() untuk hierarki pewarisan, getConstructor() untuk constructor, dan getAttributes() yang segera kita pakai beberapa bagian ke depan.

Menjalankan Method lewat ReflectionMethod #

Reflection tidak berhenti pada membaca — ReflectionMethod::invoke() mampu menjalankan method target, lengkap dengan argumennya:

php
<?php

class Kalkulator
{
    public function tambah(int $a, int $b): int
    {
        return $a + $b;
    }

    private function kodeRahasia(): string
    {
        return "kode rahasia";
    }
}

$refleksi = new ReflectionClass(Kalkulator::class);

$tambah = $refleksi->getMethod("tambah");
echo $tambah->invoke(new Kalkulator(), 7, 5) . "\n";

$rahasia = $refleksi->getMethod("kodeRahasia");
echo $rahasia->invoke(new Kalkulator()) . "\n";

Output yang Dihasilkan:

text
12
kode rahasia

Baris invoke(new Kalkulator(), 7, 5) menjalankan tambah() pada sebuah instance dengan dua argumen posisional — hasilnya sama dengan $kalkulator->tambah(7, 5). Yang mengejutkan sebagian pembaca adalah baris terakhir: method private kodeRahasia() juga berhasil dijalankan tanpa perlawanan. Sejak PHP 8.1, reflection bisa mengakses member non-publik secara langsung — tidak lagi perlu panggilan setAccessible(true) seperti era PHP 7. Kemampuan ini disediakan untuk alat-alat sah seperti test framework yang menguji method private, serializer yang membaca seluruh state, dan container yang menginisialisasi object.

Membaca dan Menulis Property Private #

Perilaku yang sama berlaku pada ReflectionProperty. Object refleksi property bisa membaca bahkan mengubah nilai property private dari luar class:

php
<?php

class Mesin
{
    public function __construct(
        private string $serialNumber = "SN-2026-001",
    ) {}
}

$properti = new ReflectionProperty(Mesin::class, "serialNumber");
$mesin = new Mesin();

var_dump($properti->isPrivate());

echo "Serial: " . $properti->getValue($mesin) . "\n";

$properti->setValue($mesin, "SN-2026-999");
echo "Serial baru: " . $properti->getValue($mesin) . "\n";

Output yang Dihasilkan:

text
bool(true)
Serial: SN-2026-001
Serial baru: SN-2026-999

isPrivate() mengonfirmasi bahwa property $serialNumber memang private — dan tetap saja getValue($mesin) berhasil membacanya. Method setValue($mesin, "SN-2026-999") kemudian menimpa nilainya langsung dari luar class, terbukti dari cetakan kedua. Method setAccessible() masih ada di API demi kompatibilitas, tetapi sejak PHP 8.1 ia tidak melakukan apa pun (no-op) karena semua member sudah dapat diakses secara bawaan.

Membaca Attribute lewat Reflection #

Inilah momen di mana dua chapter terakhir bertemu: reflection adalah satu-satunya cara membaca attribute . Method getAttributes() tanpa argumen mengembalikan seluruh attribute yang menempel:

php
<?php

#[Attribute(Attribute::TARGET_CLASS)]
class Entity
{
    public function __construct(public string $tabel) {}
}

#[Entity("pengguna")]
class Pengguna {}

$refleksi = new ReflectionClass(Pengguna::class);

foreach ($refleksi->getAttributes() as $atribut) {
    $objek = $atribut->newInstance();
    echo $atribut->getName() . " => tabel " . $objek->tabel . "\n";
}

Output yang Dihasilkan:

text
Entity => tabel pengguna

Setiap elemen hasil getAttributes() adalah ReflectionAttribute: getName() melaporkan nama class attribute-nya, dan newInstance() menjalankan constructor-nya sehingga kita memperoleh object Entity dengan property $tabel terisi "pengguna". Memberi argumen filter — getAttributes(Entity::class) — mempersempit hasil ke attribute tertentu saja, seperti yang kita lakukan pada router mini di chapter sebelumnya.

Use Case: Container Dependency Injection Mini #

Sekarang kita rakit semuanya menjadi alat yang paling banyak memanfaatkan reflection di dunia nyata: container yang menyusun grafik dependency secara otomatis. Container membaca constructor sebuah class, mencari tahu dependency yang dibutuhkannya, lalu merekursi membuat semuanya:

php
<?php

class KoneksiDatabase
{
    public function sambung(): string
    {
        return "koneksi siap";
    }
}

class PenggunaRepository
{
    public function __construct(private KoneksiDatabase $koneksi) {}

    public function semua(): string
    {
        return "daftar pengguna via " . $this->koneksi->sambung();
    }
}

class PenggunaService
{
    public function __construct(private PenggunaRepository $repository) {}

    public function tampilkan(): string
    {
        return $this->repository->semua();
    }
}

function resolve(string $namaClass): object
{
    $refleksi = new ReflectionClass($namaClass);
    $konstruktor = $refleksi->getConstructor();

    if ($konstruktor === null) {
        return $refleksi->newInstance();
    }

    $argumen = [];
    foreach ($konstruktor->getParameters() as $parameter) {
        $tipe = $parameter->getType()->getName();
        $argumen[] = resolve($tipe);
    }

    return $refleksi->newInstanceArgs($argumen);
}

$service = resolve(PenggunaService::class);
echo $service->tampilkan() . "\n";

Output yang Dihasilkan:

text
daftar pengguna via koneksi siap

Alur kerjanya patut dibedah per langkah:

  1. getConstructor(): refleksi PenggunaService ditanya constructor-nya; karena ada, kita lanjut ke pemeriksaan parameter.
  2. getParameters(): setiap ReflectionParameter menjelaskan satu parameter constructor. Di sini container sengaja sederhana: ia mengasumsikan semua parameter bertipe class dan wajib diisi.
  3. getType()->getName(): tipe deklarasi (KoneksiDatabase, PenggunaRepository) diambil sebagai string, lalu resolve() memanggil dirinya sendiri secara rekursif — dependency dependency pun tersusun otomatis sampai class tanpa constructor (KoneksiDatabase).
  4. newInstanceArgs($argumen): instance dibuat dengan daftar argument yang sudah lengkap, ibarat memanggil new PenggunaService(new PenggunaRepository(new KoneksiDatabase())) — tanpa kita menulisnya manual.

Container produksi tentu lebih canggih: menangani parameter opsional, interface binding, singleton scope, hingga autowiring berbasis attribute. Namun intinya sama — seluruh sihirnya bersumber dari reflection.

Biaya Performance dan Cara Bijak Memakainya #

Kemampuan sebesar ini datang dengan biaya. Setiap panggilan reflection melewati lapisan API object (pencarian method, pembuatan array hasil, pemeriksaan flag) — jauh lebih banyak langkah daripada pemanggilan $produk->hitungHargaJual() langsung. Mengintip struktur class di dalam loop ketat atau di setiap request tanpa penyimpanan adalah resep pemborosan. Kabar baiknya: hasil reflection bersifat deterministik untuk satu class, sehingga bisa dihitung sekali lalu disimpan:

php
<?php

class RegistryRefleksi
{
    /** @var array<string, ReflectionClass<object>> */
    private static array $cache = [];

    public static function ambil(string $namaClass): ReflectionClass
    {
        return self::$cache[$namaClass] ??= new ReflectionClass($namaClass);
    }
}

Registry di atas menyimpan object ReflectionClass di property static, sehingga class yang sama tidak dibedah dua kali dalam satu request. Framework besar melangkah lebih jauh: Symfony mengompilasi definisi container menjadi class PHP biasa saat deployment, sehingga reflection hanya berjalan sekali di masa build — request-runtime tinggal menjalankan new biasa. Prinsip praktisnya: gunakan reflection di batas sistem (boot, konfigurasi, tooling), cache hasilnya, dan biarkan jalur panas (hot path) aplikasi memakai pemanggilan langsung.

Ringkasan #

Reflection adalah kemampuan PHP melihat dan menggerakkan struktur kodenya sendiri: ReflectionClass menelusuri method, property, dan constructor; ReflectionMethod::invoke() serta ReflectionProperty bisa menjalankan dan mengubah member mana pun; dan getAttributes() menghidupkan metadata dari chapter sebelumnya. Kombinasi ini menjadi fondasi container, ORM, serializer, dan test framework — dengan catatan biaya performance yang ditenangkan lewat caching. Fitur mutakhir object model PHP belum habis di sini; berikutnya kita beralih ke pemrosesan data hemat memori lewat generators dan iterators.

Referensi #