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
Menumemakai dua trait yang sama-sama punya methodrupiah(). 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
trait FormatRupiah { ... }mendeklarasikan trait. Isinya method biasa.use FormatRupiah;di dalam badan class memasang trait itu. Sesudahnyarupiah()terpakai seolah ditulis di class itu sendiri.$thisdi dalam method trait adalah objek class yang memakainya. Trait gak menyimpan datanya sendiri, jadi method-nya membaca property milik class: kalau trait menulis$this->nama, class-nya harus punya property$nama.- Fungsi string dari lesson String jalan seperti biasa di dalam trait, misalnya
strtoupper($this->nama)untuk huruf besar danstrtolower($this->nama)untuk huruf kecil. number_format($angka, 0, ",", ".")menulis angka dengan titik ribuan, cara Indonesia.
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
insteadofmemilih:rupiah()milikFormatRupiahdipakai, bukan milikFormatSingkat.asmemberi nama kedua: versiFormatSingkattetap ada sebagairupiahSingkat().
💡 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;
}
}
- Class yang memasang
BisaDiskonwajib menulisharga(): int. Lupa, dan PHP menolak:Class Minuman contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Minuman::harga). - Dengan
harga()15000,hargaDiskon(10)mengembalikan13500. - Property
staticdisalin ke tiap class:Menu::tambah()dua kali laluPesanan::tambah()sekali menghasilkan 1, 2, lalu 1. Hitungannya gak dibagi.
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
usedi tempat yang salah.usedi atas file adalah import namespace (lesson Namespace & Autoloading). Trait dipasang denganusedi dalam badan class.- Nama property gak cocok. Trait yang membaca
$this->namadi class yang property-nya$namemenghasilkanWarning: Undefined property: Kota::$nama, dan hasilnya kosong. - Dua trait, satu nama method, tanpa
insteadof. File-nya gak jalan sama sekali. Pilih denganinsteadof, simpan yang lain denganas. - Trait untuk segalanya. Kalau method-nya cuma dipakai satu class, tulis saja di class itu.
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".