Trait adalah mekanisme pemakaian ulang kode secara horizontal, yaitu menyalin sekumpulan metode dan properti ke dalam kelas apa pun yang membutuhkannya tanpa melalui garis pewarisan. Topik ini membahas deklarasi trait dan pemakaiannya, penyelesaian konflik metode dengan insteadof dan as, serta metode abstrak di dalam trait yang menuntut kelengkapan dari kelas penerimanya. Contohnya tetap di domain perpustakaan, yaitu pelacakan peminjaman dan pelabelan koleksi yang dibutuhkan oleh kelas-kelas yang tidak saling mewarisi.
Membagikan kode dengan trait#
Trait dideklarasikan dengan kata kunci trait dan isinya ditulis seperti tubuh kelas, yaitu properti beserta metode yang lengkap. Kelas memakainya dengan kata kunci use diikuti nama trait-nya, dan seluruh anggota trait menyatu ke dalam kelas tersebut seolah ditulis langsung di tubuhnya. Anggota itu tidak dapat dipanggil dari dalam trait itu sendiri karena trait tidak dapat dijadikan objek, melainkan selalu dipanggil melalui objek milik kelas penerimanya.
Manfaat utamanya terlihat ketika dua kelas yang tidak memiliki hubungan pewarisan membutuhkan perilaku yang sama. Menempatkan perilaku itu pada kelas induk bersama akan memaksakan relasi yang tidak sejati, sedangkan menyalin kode secara manual menimbulkan dua tempat yang harus diubah bersamaan. Trait menyelesaikan keduanya, yaitu kode ditulis sekali dan disematkan ke kelas mana pun yang membutuhkannya.
Skrip berikut membuat trait Pelacak yang menghitung jumlah peminjaman, lalu menyematkannya ke kelas Buku dan Laptop yang tidak saling mewarisi. Setiap objek membawa keadaan hitungannya sendiri karena properti yang menyatu tetap berperilaku sebagai properti objek biasa.
<?php
declare(strict_types=1);
trait Pelacak
{
private int $jumlahDipinjam = 0;
public function catatPinjam(): void
{
$this->jumlahDipinjam++;
}
public function jumlahDipinjam(): int
{
return $this->jumlahDipinjam;
}
}
class Buku
{
use Pelacak;
public function __construct(public string $judul)
{
}
}
class Laptop
{
use Pelacak;
public function __construct(public string $unit)
{
}
}
$buku = new Buku('Algoritma Dasar');
$laptop = new Laptop('LP-11');
$buku->catatPinjam();
$buku->catatPinjam();
$laptop->catatPinjam();
echo $buku->jumlahDipinjam(), "\n";
echo $laptop->jumlahDipinjam(), "\n";2
1Keluaran menunjukkan penghitung masing-masing objek berjalan terpisah meskipun kodenya berasal dari satu trait yang sama. Metode catatPinjam() dan jumlahDipinjam() kini dapat dipanggil pada kedua kelas, dan cukup satu baris use Pelacak; pada tubuh kelas untuk mengetahui sumber kemampuan tersebut.
Menyelesaikan konflik metode#
Apabila dua trait yang disematkan pada satu kelas memuat metode bernama sama, PHP tidak memilih salah satunya secara diam-diam. Konflik semacam ini menggagalkan pemuatan kelas dengan error fatal, sehingga harus diselesaikan secara eksplisit pada baris use. Dua kata kunci yang disediakan adalah insteadof untuk memilih satu metode yang mewakili nama tersebut, dan as untuk mengganti nama atau mengubah visibilitas metode lain.
Bentuk penulisannya berada di dalam kurung kurawal yang mengikuti daftar trait, dan penyelesaiannya dinyatakan per konflik. Alias yang dibuat dengan as tidak menggantikan metode aslinya, melainkan menambahkan nama lain untuk memanggil versi yang kalah konflik, sehingga kedua perilaku tetap dapat dijangkau.
Skrip berikut menyematkan dua trait yang sama-sama menyediakan label(), lalu menetapkan LabelKode::label sebagai pemenangnya dan menyimpan versi LabelPanjang di bawah nama labelPanjang().
<?php
declare(strict_types=1);
trait LabelKode
{
public function label(): string
{
return "KODE-{$this->judul}";
}
}
trait LabelPanjang
{
public function label(): string
{
return "{$this->judul} (label lengkap)";
}
}
class Buku
{
use LabelKode, LabelPanjang {
LabelKode::label insteadof LabelPanjang;
LabelPanjang::label as labelPanjang;
}
public function __construct(public string $judul)
{
}
}
$buku = new Buku('Algoritma Dasar');
echo $buku->label(), "\n";
echo $buku->labelPanjang(), "\n";KODE-Algoritma Dasar
Algoritma Dasar (label lengkap)Baris pertama keluaran berasal dari LabelKode sesuai keputusan insteadof, sedangkan baris kedua mengakses versi LabelPanjang melalui nama aliasnya. Konflik yang dibiarkan tanpa penyelesaian menggagalkan pemuatan kelas sebelum program berjalan, sebagaimana diperlihatkan fragmen berikut yang tidak dijalankan karena memang tidak dapat dimuat.
// tidak-dijalankan
class Buku
{
use LabelKode, LabelPanjang;
}
// Deklarasi di atas gagal dimuat dengan error fatal berpola:
// Fatal error: Trait method LabelPanjang::label has not been applied as
// Buku::label, because of collision with LabelKode::label
Metode abstrak di dalam trait#
Trait boleh menyatakan metode abstrak, yaitu deklarasi tanda tangan tanpa tubuh yang wajib dilengkapi oleh kelas penerimanya. Mekanisme ini menjadikan trait sebagai kolaborator yang ringkas, karena metode bersama di dalam trait dapat mengandalkan kemampuan yang dijanjikan kelas penerimanya tanpa perlu mengetahui bentuk kelas tersebut. Kelas yang melupakan satu metode abstrak pun gagal dideklarasikan, sama seperti kewajiban pada kelas abstrak dan interface.
Pola yang lazim adalah trait menyediakan metode penyusun, sedangkan kelas penerimanya memberikan bahan yang disusun. Dengan begitu, satu-satunya bagian yang berbeda antar kelas adalah bahan tersebut, sedangkan cara menyusunnya ditulis sekali di dalam trait.
Skrip berikut membuat trait Berlabel yang menuntut metode kodeLabel() dan menyediakan label() yang memakainya. Dua kelas penerima trait ini memberikan isi kodeLabel() yang berbeda, dan label() yang sama menghasilkan teks yang mengikutinya.
<?php
declare(strict_types=1);
trait Berlabel
{
abstract public function kodeLabel(): string;
public function label(): string
{
return "({$this->kodeLabel()})";
}
}
class Buku
{
use Berlabel;
public function __construct(
public string $judul,
public string $kode,
) {
}
public function kodeLabel(): string
{
return "BK-{$this->kode}";
}
}
class Majalah
{
use Berlabel;
public function __construct(
public string $judul,
public int $edisi,
) {
}
public function kodeLabel(): string
{
return "MJ-edisi-{$this->edisi}";
}
}
$buku = new Buku('Algoritma Dasar', '014');
$majalah = new Majalah('Jurnal Kampus', 12);
echo $buku->label(), "\n";
echo $majalah->label(), "\n";(BK-014)
(MJ-edisi-12)Kedua baris keluaran membuktikan bahwa label() yang sama menghasilkan susunan yang berbeda mengikuti kodeLabel() milik masing-masing kelas. Pola ini melengkapi kontrak interface yang lebih besar, karena metode abstrak di dalam trait berperan sebagai kontrak kecil antara trait dan kelas yang menerimanya.
Kesalahan umum#
- Membiarkan konflik metode tanpa penyelesaian. Kelas dengan dua trait yang menyediakan metode bernama sama gagal dimuat dengan error fatal, dan kegagalannya terjadi sebelum program berjalan. Selesaikan dengan
insteadofdanaspada barisuse, atau susun ulang isi trait agar tidak bertabrakan. - Mengira metode milik kelas selalu menang atas metode trait. Metode yang ditulis langsung di kelas memang menimpa metode trait bernama sama tanpa peringatan apa pun, sehingga menghapus atau mengganti nama metode kelas dapat memunculkan versi trait secara diam-diam. Periksa daftar
usekelas tersebut apabila perilaku metode berubah tanpa sebab yang terlihat. - Menumpuk banyak trait pada satu kelas. Kelas yang menerima sepuluh trait menjadi sulit ditelusuri karena asal setiap metode tersebar di banyak berkas. Batasi penggunaan trait pada kemampuan yang benar-benar kecil dan berdiri sendiri, dan pilih pewarisan, komposisi, atau interface bila relasinya lebih besar.
- Menyematkan trait yang bergantung pada anggota kelas tanpa menegakkannya. Trait yang membaca
$this->kodeakan berjalan lancar pada satu kelas dan memicu error properti tidak terdefinisi pada kelas lain yang tidak memilikinya. Nyatakan kebutuhan tersebut sebagai metode abstrak di dalam trait agar kekurangannya tertangkap pada saat deklarasi kelas.
Ringkasan#
- Trait menyalin metode dan properti ke kelas mana pun melalui
use, sehingga kode dibagikan secara horizontal tanpa harus membuat relasi pewarisan yang tidak sejati. - Konflik metode antar trait wajib diselesaikan dengan
insteadofuntuk memilih pemenang danasuntuk alias atau perubahan visibilitas, karena konflik yang dibiarkan menggagalkan pemuatan kelas. - Metode kelas yang ditulis langsung di tubuh kelas menimpa metode trait bernama sama tanpa peringatan.
- Metode abstrak di dalam trait menuntut kelas penerimanya menyediakan bahan tertentu, sehingga trait dapat menyediakan susunan bersama dengan bahan yang berbeda per kelas.
Referensi#
- Traits — Manual PHP — Menetapkan semantik trait, penyelesaian konflik, dan metode abstrak di dalamnya.
- Object Inheritance — Manual PHP — Menjadi pembanding aturan penimpaan metode pada pewarisan dan trait.