Banyak data dalam aplikasi hanya boleh berisi sejumlah nilai tetap: status pesanan (menunggu, diproses, selesai), peran pengguna (tamu, member, admin), atau hari kerja. Selama ini PHP hanya menyediakan konstanta class atau string mentah untuk kebutuhan tersebut — dan keduanya rawan bug: salah kapital "Aktif" vs "aktif" tidak tertangkap sampai produksi. PHP 8.1 membawa solusi native berupa enumeration (enum): tipe data yang nilai-nilainya terdaftar eksplisit di dalam deklarasi. Pada chapter ini kita pelajari enum murni (pure enum): cara mendefinisikannya, sifat singleton tiap case-nya, hingga method, interface, dan trait di dalamnya.

Masalah Kumpulan String yang Menyebarkan Bug #

Mari kita lihat betapa rapuhnya pendekatan lama. Bayangkan sebuah fungsi yang menerima status pengguna sebagai string:

php
<?php

function bisaLogin(string $status): bool
{
    return in_array($status, ["aktif", "AKTIF", "Aktif"], true);
}

var_dump(bisaLogin("aktif"));
var_dump(bisaLogin("aktiff"));

Output yang Dihasilkan:

text
bool(true)
bool(false)

Typo "aktiff" dan variasi kapitalisasi lolos dari pemeriksaan tipe karena keduanya tetap string yang sah. Compiler tidak bisa membantu — semua string dianggap benar. Enum mengubah situasi ini: nilai valid menjadi case yang terdaftar, dan parameter bertipe enum hanya menerima case tersebut; jika nanti kita salah menulis nama case, PHP langsung memunculkan Error — bukan bug senyap di production.

Mendefinisikan enum #

Enum dideklarasikan dengan kata kunci enum, diikuti daftar case. Setiap case adalah konstanta milik enum tersebut:

php
<?php

enum Status
{
    case Aktif;
    case Nonaktif;
    case Ditangguhkan;
}

$status = Status::Aktif;

var_dump($status === Status::Aktif);
var_dump($status instanceof Status);
var_dump($status);

Output yang Dihasilkan:

text
bool(true)
bool(true)
enum(Status::Aktif)

Mari kita bedah hasilnya baris per baris:

  1. enum Status { case Aktif; ... }: kita mendaftarkan tiga nilai sah untuk tipe Status. Penulisan case biasa diawali huruf kapital mengikuti konvensi konstanta.
  2. $status === Status::Aktif → bool(true): setiap case adalah instance tunggal (singleton) dari class enum. Status::Aktif yang diambil dari titik mana pun di program selalu menunjuk object yang sama, sehingga perbandingan identitas === dijamin akurat.
  3. $status instanceof Status → bool(true): case enum adalah object — instance dari class Status. Karena itu enum bisa diketik sebagai parameter: function aktifkan(Status $status).
  4. var_dump($status) → enum(Status::Aktif): representasi ringkas ini juga muncul di pesan error dan stack trace, membuat debugging lebih mudah.

Perhatikan bahwa tidak ada new di mana pun: kita mengakses case lewat operator :: seperti konstanta class, dan PHP yang mengelola pembuatan instancenya.

Menjelajahi Semua Case dengan cases() #

Enum menyediakan method statis cases() yang mengembalikan seluruh case dalam urutan deklarasi. Method ini berguna untuk mengisi dropdown formulir, membuat tabel referensi, atau menulis pengujian otomatis:

php
<?php

enum Status
{
    case Aktif;
    case Nonaktif;
    case Ditangguhkan;
}

foreach (Status::cases() as $status) {
    echo $status->name . "\n";
}

Output yang Dihasilkan:

text
Aktif
Nonaktif
Ditangguhkan

Setiap case memiliki property read-only $name yang berisi nama case sebagai string — pada contoh di atas kita memakainya untuk mencetak ketiga nama tanpa menulisnya manual satu per satu. Method cases() mengembalikan array, jadi semua fungsi array seperti count(), array_map(), atau array_filter() bisa langsung dipakai.

Enum dalam Ekspresi match #

Karena setiap case adalah singleton, enum berpasangan sempurna dengan ekspresi match . Pencocokan dilakukan terhadap case-nya, bukan string:

php
<?php

enum Status
{
    case Aktif;
    case Nonaktif;
    case Ditangguhkan;
}

function deskripsiStatus(Status $status): string
{
    return match ($status) {
        Status::Aktif => "Pengguna aktif dan bisa login",
        Status::Nonaktif => "Pengguna nonaktif dan tidak bisa login",
        Status::Ditangguhkan => "Akun ditangguhkan sementara",
    };
}

echo deskripsiStatus(Status::Ditangguhkan) . "\n";

Output yang Dihasilkan:

text
Akun ditangguhkan sementara

Subjek match ($status) dibandingkan secara identik (===) dengan setiap case di sisi kiri. Jika suatu saat Anda menambahkan case baru namun lupa menambahkan cabang match, match tanpa arm default melempar UnmatchedError — kegagalan yang terjadi di tempat kejarian, bukan perilaku senyap. Ini alasan banyak tim menjadikan match non-exhaustive sebagai alarm saat refactor enum.

Method, Interface, dan Trait pada Enum #

Enum adalah class di balik layar, sehingga bisa memiliki method, konstanta, bahkan mengimplementasikan interface. Fitur ini membuat data enum “pintar”: logika yang selama ini tersebar di switch bisa dipindahkan menjadi method pada enum itu sendiri:

php
<?php

interface BisaDiberiLabel
{
    public function label(): string;
}

enum Status implements BisaDiberiLabel
{
    case Aktif;
    case Nonaktif;
    case Ditangguhkan;

    public function label(): string
    {
        return match ($this) {
            self::Aktif => "Aktif (bisa login)",
            self::Nonaktif => "Nonaktif (tidak bisa login)",
            self::Ditangguhkan => "Ditangguhkan (menunggu verifikasi)",
        };
    }
}

foreach (Status::cases() as $status) {
    echo $status->name . " => " . $status->label() . "\n";
}

Output yang Dihasilkan:

text
Aktif => Aktif (bisa login)
Nonaktif => Nonaktif (tidak bisa login)
Ditangguhkan => Ditangguhkan (menunggu verifikasi)

Di dalam method enum, $this merujuk pada case yang sedang dipanggil — pada pemanggilan Status::Ditangguhkan->label(), nilai $this adalah case Ditangguhkan. Prefix self::Aktif di dalam match mengacu pada case milik enum itu sendiri. Interface BisaDiberiLabel dipenuhi seperti class biasa, sehingga enum kita bisa masuk ke fungsi bertipe interface tersebut.

Trait juga bisa dipakai untuk berbagi method antar enum, dan konstanta class tetap tersedia:

php
<?php

trait BisaDideskripsikan
{
    public function deskripsiPendek(): string
    {
        return "Kategori: " . $this->name;
    }
}

enum Prioritas
{
    use BisaDideskripsikan;

    case Rendah;
    case Sedang;
    case Tinggi;

    public const DEFAULT = self::Sedang;
}

echo Prioritas::DEFAULT->deskripsiPendek() . "\n";
echo Prioritas::Tinggi->deskripsiPendek() . "\n";

Output yang Dihasilkan:

text
Kategori: Sedang
Kategori: Tinggi

Trait BisaDideskripsikan menyuntikkan method deskripsiPendek() ke enum Prioritas tanpa menulis ulang. Konstanta DEFAULT menyimpan case default aplikasi — karena case adalah object, konstanta ini bisa langsung dipanggil method-nya seperti terlihat pada baris terakhir.

Larangan yang Menjaga Keunikan Case #

PHP memagari enum dengan beberapa larangan yang membuat case selalu unik dan representatif. Enum tidak bisa diinstansiasi langsung:

php
<?php

enum Status
{
    case Aktif;
    case Nonaktif;
}

$status = new Status();

Output yang Dihasilkan:

text

Fatal error: Uncaught Error: Cannot instantiate enum Status in /home/z/my-project/php-course/content/php/advanced-object-model/enums/.verify_tmp.php:9
Stack trace:
#0 {main}
  thrown in /home/z/my-project/php-course/content/php/advanced-object-model/enums/.verify_tmp.php on line 9

Fatal error Cannot instantiate enum Status menegaskan bahwa satu-satunya jalan mendapatkan instance Status adalah lewat case yang terdaftar. Larangan serupa berlaku untuk clone Status::Aktif yang melempar Trying to clone an uncloneable object, dan mendeklarasikan property di dalam enum yang ditolak pesan Enum cannot include properties — memastikan tidak ada state liar yang menempel pada case. Akibatnya jumlah instance Status di seluruh program selalu tepat sebanyak case-nya: tidak lebih, tidak kurang.

Ringkasan #

Enum memindahkan kumpulan nilai tetap dari wilayah konvensi (“string ini saja ya”) ke wilayah sistem tipe: case terdaftar, singleton, dan dijaga larangan instansiasi. Kita sudah bisa mendefinisikan enum, menjelajahinya dengan cases(), memakainya di match, serta memperkayanya dengan method, interface, dan trait. Namun enum murni tidak membawa data — $status->value tidak ada. Chapter berikutnya memperkenalkan backed enum yang menautkan setiap case dengan nilai skalar int atau string, sehingga cocok untuk integrasi dengan database dan API.

Referensi #