Trait & Interface — PHP

Interface mendefinisikan kontrak — method apa yang harus diimplementasikan. Trait berbagi implementasi antar class. Interface: interface Printable { public…

Trait adalah kumpulan method yang bisa dipasang ke banyak class sekaligus, tanpa hubungan induk dan anak. Bayangin stempel harga di Warung Bu Tini yang mencetak "Rp15.000". Papan menu memakainya, struk juga memakainya, padahal papan menu bukan struk dan struk bukan papan menu. Trait adalah stempel itu: satu potong kode, dipakai di class yang gak bersaudara. Saat file dibaca, PHP menyalin method trait ke dalam tiap class yang memakainya.

🧪 Tebakan cepat: class Menu memakai dua trait yang sama-sama punya method rupiah(). Apa yang terjadi saat file dijalankan? Tebak dulu, jawabannya ada di bagian "Dua trait, nama method sama".

Trait: method yang dipakai bersama

<?php
trait FormatRupiah {
    public function rupiah(int $angka): string {
        return "Rp" . number_format($angka, 0, ",", ".");
    }
}

class Menu {
    use FormatRupiah;

    public function __construct(public string $nama, public int $harga) {}

    public function label(): string {
        return $this->nama . " " . $this->rupiah($this->harga);
    }
}

class Pesanan {
    use FormatRupiah;

    public function __construct(public int $total) {}

    public function struk(): string {
        return "Total " . $this->rupiah($this->total);
    }
}

echo (new Menu("Nasi Goreng", 15000))->label() . "\n";
echo (new Pesanan(20000))->struk() . "\n";
Nasi Goreng Rp15.000
Total Rp20.000

Kenapa gak pakai extends?

Di OOP Lanjutan, Makanan extends Menu. Tapi satu class PHP cuma boleh punya satu induk. Pesanan bukan Menu, jadi menurunkannya dari Menu cuma demi rupiah() itu salah arti. Trait menyelesaikannya dari samping: class mana pun boleh memasangnya, sebanyak apa pun, dan satu class boleh memasang beberapa trait sekaligus dengan use FormatRupiah, PunyaDiskon;. Itu keunggulan utama trait: bisa dipakai di banyak class, tanpa batasan satu induk.

Dua trait, nama method sama

trait FormatSingkat {
    public function rupiah(int $angka): string {
        return intdiv($angka, 1000) . "rb";
    }
}

class Menu {
    use FormatRupiah, FormatSingkat;
    // ...
}

PHP menolak file-nya sebelum satu baris pun jalan:

PHP Fatal error:  Trait method FormatSingkat::rupiah has not been applied as Menu::rupiah, because of collision with FormatRupiah::rupiah in /app/menu.php on line 14

Dua trait menyalin rupiah() ke class yang sama, dan PHP gak menebak mana yang kamu mau. Itu jawaban tebakannya. Kamu yang memutuskan, di blok { } sesudah use:

class Menu {
    use FormatRupiah, FormatSingkat {
        FormatRupiah::rupiah insteadof FormatSingkat;
        FormatSingkat::rupiah as rupiahSingkat;
    }
    // ...
}

$menu = new Menu("Nasi Goreng", 15000);
echo $menu->label() . "\n";
echo $menu->rupiahSingkat(15000) . "\n";
Nasi Goreng Rp15.000
15rb

💡 Abstract dan static di trait

Trait boleh meminta method dari class pemakainya dengan abstract, dan boleh punya anggota static:

trait BisaDiskon {
    abstract public function harga(): int;

    public function hargaDiskon(int $persen): int {
        return $this->harga() - intdiv($this->harga() * $persen, 100);
    }
}

trait Penghitung {
    private static int $jumlah = 0;

    public static function tambah(): int {
        return ++self::$jumlah;
    }
}

Trait, interface, atau extends?

Alat Menjawab Contoh
interface apa yang harus bisa dilakukan, tanpa isi MetodeBayar dengan bayar()
trait bagaimana caranya, isi yang disalin FormatRupiah dengan rupiah()
extends class ini adalah jenis dari class lain, satu induk saja Makanan extends Menu

Trait bukan tipe. new FormatRupiah() ditolak dengan Error: Cannot instantiate trait FormatRupiah, dan parameter FormatRupiah $x gak pernah cocok dengan objek apa pun. Kalau butuh tipe, pasangkan dengan interface: class Menu implements BisaDiformat { use FormatRupiah; }. Interface yang menjanjikan, trait yang mengisi.

⚠️ Jebakan yang sering ketemu

TL;DR: trait Nama { ... } berisi method yang dipakai bersama, dan use Nama; di dalam badan class memasangnya, seolah method-nya ditulis di class itu. $this di trait adalah objek pemakainya, jadi trait membaca property milik class. Keunggulannya: bisa dipakai di banyak class tanpa batasan satu induk extends, dan satu class boleh use A, B;. Dua trait dengan method bernama sama membuat Fatal error collision, yang diselesaikan dengan insteadof dan as. Trait boleh punya method abstract (wajib ditulis class pemakainya) dan anggota static (salinan per class). Trait bukan tipe dan gak bisa di-new: interface menjanjikan apa, trait mengisi bagaimana, extends menyatakan "adalah".

Yang akan kamu pelajari