Hampir setiap aplikasi memiliki sekumpulan nilai tetap: status pesanan, peran pengguna, arah pengurutan. Selama dua puluh tahun lebih, PHP tidak punya konstruksi bahasa untuk kebutuhan tersebut, sehingga kita berkerja dengan string, konstanta class, atau peniruan manual yang rawan salah ketik. PHP 8.1 akhirnya menghadirkan enum — tipe data yang daftar nilainya tertutup dan diperiksa interpreter. Chapter ini menelusuri evolusinya: seperti apa penderitaan pola lama, bagaimana enum menuntaskannya, dan mengapa setiap case enum adalah singleton yang bisa dibandingkan dengan operator identitas. Pembahasan mendalam tentang enum sebagai object — trait, interface, dan serialization — tersedia di Section Advanced Object Model .

Pola Lama: Konstanta Class dan String Bebas #

Cara paling umum sebelum PHP 8.1 adalah mengumpulkan string status sebagai konstanta class. Parameter function tetap bertipe string, sehingga interpreter tidak punya cara membedakan string valid dari string salah ketik:

php
<?php
class StatusPesananLama
{
    public const MENUNGGU = 'menunggu';
    public const DIPROSES = 'diproses';
    public const SELESAI = 'selesai';
}

function labelStatus(string $status): string
{
    return match ($status) {
        StatusPesananLama::MENUNGGU => 'Menunggu pembayaran',
        StatusPesananLama::DIPROSES => 'Sedang diproses',
        StatusPesananLama::SELESAI => 'Selesai',
        default => 'Status tidak dikenal',
    };
}

// Salah ketik satu huruf tetap lolos karena parameter bertipe string
echo labelStatus('menungggu'), "\n";

Output yang Dihasilkan:

text
Status tidak dikenal

Pemanggilan di atas tidak memicu error apa pun; kekeliruan ketik menungggu jatuh ke cabang default dan berakhir sebagai Status tidak dikenal. Bug semacam ini bisa lolos dari pengujian bila cabang default tidak diuji, dan satu-satunya pertahanan hanyalah disiplin menulis StatusPesananLama::MENUNGGU di setiap pemanggilan.

Untuk mendapatkan jaminan tipe, sebagian kita meniru enum dengan class final, constructor private, dan cache instansinya. Hasilnya bekerja, tetapi mahal:

php
<?php
final class StatusLama
{
    private static array $instances = [];

    private function __construct(private string $nilai)
    {
    }

    public static function dari(string $nilai): ?self
    {
        if (!in_array($nilai, ['menunggu', 'diproses'], true)) {
            return null;
        }
        return self::$instances[$nilai] ??= new self($nilai);
    }

    public function label(): string
    {
        return match ($this->nilai) {
            'menunggu' => 'Menunggu pembayaran',
            'diproses' => 'Sedang diproses',
        };
    }
}

$a = StatusLama::dari('menunggu');
$b = StatusLama::dari('menunggu');

var_dump($a === $b);
echo $a->label(), "\n";
var_dump(StatusLama::dari('batal'));

Output yang Dihasilkan:

text
bool(true)
Menunggu pembayaran
NULL

Class tiruan ini memang berhasil: bool(true) membuktikan dari() mengembalikan instansi yang sama (identik) untuk nilai yang sama, dan dari('batal') mengembalikan NULL alih-alih status palsu. Sayangnya, satu nilai butuh cache statis, validasi manual, dan constructor private — dan setiap kelompok nilai baru mengulang seluruh cetak biru tersebut.

Enum di PHP 8.1: Daftar Nilai Tertutup #

Enum menggantikan kedua pola di atas dengan satu konstruksi bawaan. Kata kunci enum mendeklarasikan daftar case; daftar tersebut menjadi satu-satunya nilai yang sah untuk tipe tersebut:

php
<?php
enum StatusPesanan
{
    case Menunggu;
    case Diproses;
    case Selesai;

    public function label(): string
    {
        return match ($this) {
            self::Menunggu => 'Menunggu pembayaran',
            self::Diproses => 'Sedang diproses',
            self::Selesai => 'Selesai',
        };
    }
}

function labelStatus(StatusPesanan $status): string
{
    return $status->label();
}

echo labelStatus(StatusPesanan::Diproses), "\n";
echo StatusPesanan::Menunggu->label(), "\n";

Output yang Dihasilkan:

text
Sedang diproses
Menunggu pembayaran

Mari kita bedah potongan di atas:

  1. case Menunggu; dan kawan-kawannya: tiga nilai sah dari tipe StatusPesanan — tidak ada yang lain.
  2. Method label(): enum boleh memiliki method, dan $this di dalamnya merujuk case yang sedang dipakai; match ($this) mencocokkan case satu per satu tanpa perlu cabang default karena compiler menjamin kecocokan selalu terjadi.
  3. Parameter StatusPesanan $status: kini function menolak nilai apa pun selain salah satu dari tiga case tersebut.

Perhatikan bahwa method pada enum menghapus kebutuhan match tersebar di seluruh basis kode: logika per-status tinggal di satu tempat, tepat di samping daftar nilainya.

Setiap Case adalah Singleton #

Case enum bukan sekadar konstanta bernama — setiap case adalah instansi tunggal dari tipe enum itu. Dua referensi ke StatusPesanan::Menunggu selalu menunjuk object yang sama, sehingga perbandingan === aman dan murah:

php
<?php
enum StatusPesanan
{
    case Menunggu;
    case Diproses;
    case Selesai;
}

$a = StatusPesanan::Menunggu;
$b = StatusPesanan::Menunggu;

var_dump($a === $b);
var_dump($a instanceof StatusPesanan);
var_dump($a);

Output yang Dihasilkan:

text
bool(true)
bool(true)
enum(StatusPesanan::Menunggu)

Baris pertama (bool(true)) mengonfirmasi identitas instansi, baris kedua menegaskan case adalah instansi dari tipe enum itu sendiri, dan var_dump menampilkan bentuk ringkasnya. Inilah yang dulu harus dibeli mahal dengan cache statis self::$instances pada class tiruan — enum menyediakannya gratis, tanpa kode tambahan apa pun.

Keamanan Tipe di Titik Pemanggilan #

Manfaat terbesar enum muncul justru saat programmer salah. Coba kirim string ke function yang menuntut enum:

php
<?php
enum StatusPesanan
{
    case Menunggu;
    case Diproses;
    case Selesai;
}

function labelStatus(StatusPesanan $status): string
{
    return $status->name;
}

// String tidak lagi bisa menyamar menjadi status
echo labelStatus('selesai');

Output yang Dihasilkan:

text

Fatal error: Uncaught TypeError: labelStatus(): Argument #1 ($status) must be of type StatusPesanan, string given, called in /home/z/my-project/php-course/content/php/modern-php/enums/.verify_tmp.php on line 15 and defined in /home/z/my-project/php-course/content/php/modern-php/enums/.verify_tmp.php:9
Stack trace:
#0 /home/z/my-project/php-course/content/php/modern-php/enums/.verify_tmp.php(15): labelStatus('selesai')
#1 {main}
  thrown in /home/z/my-project/php-course/content/php/modern-php/enums/.verify_tmp.php on line 9

Eksekusi berhenti dengan TypeError sebelum baris terakhir sempat berjalan — interpreter menolak string untuk parameter bertipe StatusPesanan. Pada pola konstanta lama, kekeliruan serupa berjalan diam-diam sampai menyebabkan bug logika di tempat yang jauh dari sumber kesalahan.

Sekilas Backed Enum #

Ketika nilai perlu bertukar dengan dunia luar — disimpan ke database atau dikirim sebagai JSON — enum dapat membawa nilai skalar:

php
<?php
enum Peran: string
{
    case Admin = 'admin';
    case Editor = 'editor';
    case Pembaca = 'pembaca';
}

foreach (Peran::cases() as $peran) {
    printf("%s => %s\n", $peran->name, $peran->value);
}

var_dump(Peran::tryFrom('editor'));
var_dump(Peran::tryFrom('manager'));

Output yang Dihasilkan:

text
Admin => admin
Editor => editor
Pembaca => pembaca
enum(Peran::Editor)
NULL

Method statis cases() mengembalikan seluruh case sesuai urutan deklarasi — berguna untuk mengisi dropdown atau validasi masukan. Properti $name dan $value membedakan identitas case dari nilai skalar yang dibawanya, dan tryFrom('manager') yang mengembalikan NULL menunjukkan nilai asing tetap tertolak. Serialization, trait pada enum, dan pola integrasi database dibahas tuntas pada chapter backed enums di Section Advanced Object Model .

Ringkasan #

Enum menghapus dua pola lama sekaligus: konstanta string yang menerima salah ketik, dan class final dengan constructor private yang meniru singleton secara manual. Kita mendapatkan daftar nilai tertutup dengan method sendiri, identitas instansi yang menjamin perbandingan ===, dan TypeError yang menyala tepat di titik kesalahan. Fitur ini menyempurnakan cerita besar PHP 8.1 — dan masih di versi yang sama, PHP juga memperkenalkan cara membekukan data object, yang menjadi topik chapter berikutnya.

Referensi #