Trait — Code Reuse Tanpa Inheritance

Di PHP, satu class hanya bisa extends satu parent class. Ini disebut single inheritance. Lalu bagaimana kalau kamu punya logika yang ingin dibagikan ke banyak class yang tidak saling berhubungan?

Jawabnya: Trait.

1. Masalah yang Dipecahkan Trait

Bayangkan kamu punya 3 class yang tidak ada hubungan inheritance, tapi semua butuh fitur "created_at" dan "updated_at":

<?php

// ❌ Tanpa Trait: Copy-paste kode yang sama di 3 class berbeda
class User
{
    public string $createdAt;
    public string $updatedAt;

    public function setTimestamps(): void
    {
        $this->createdAt = date('Y-m-d H:i:s');
        $this->updatedAt = date('Y-m-d H:i:s');
    }
}

class Post
{
    public string $createdAt;
    public string $updatedAt;

    public function setTimestamps(): void // Copy-paste! 😩
    {
        $this->createdAt = date('Y-m-d H:i:s');
        $this->updatedAt = date('Y-m-d H:i:s');
    }
}

class Comment
{
    public string $createdAt;
    public string $updatedAt;

    public function setTimestamps(): void // Copy-paste lagi! 😩
    {
        $this->createdAt = date('Y-m-d H:i:s');
        $this->updatedAt = date('Y-m-d H:i:s');
    }
}

Kode yang sama copy-paste 3 kali — melanggar prinsip DRY (Don't Repeat Yourself).


2. Solusi: Trait

Trait adalah "potongan kode" yang bisa di-tempelkan ke class manapun menggunakan keyword use:

<?php
declare(strict_types=1);

// ✅ Definisikan logika SEKALI di Trait
trait HasTimestamps
{
    public string $createdAt;
    public string $updatedAt;

    public function setTimestamps(): void
    {
        $now = date('Y-m-d H:i:s');
        $this->createdAt = $this->createdAt ?? $now;
        $this->updatedAt = $now;
    }

    public function getCreatedAt(): string
    {
        return $this->createdAt;
    }
}

// ✅ Tempelkan ke class manapun yang butuh
class User
{
    use HasTimestamps;

    public function __construct(
        public string $name,
        public string $email,
    ) {
        $this->setTimestamps();
    }
}

class Post
{
    use HasTimestamps;

    public function __construct(
        public string $title,
        public string $body,
    ) {
        $this->setTimestamps();
    }
}
<?php

$user = new User("Ahmad", "[email protected]");
echo $user->getCreatedAt(); // 2026-04-11 10:30:00

$post = new Post("Belajar Trait", "Trait itu keren!");
echo $post->getCreatedAt(); // 2026-04-11 10:30:00
Analogi Trait

Trait itu seperti stiker kemampuan yang bisa ditempel ke objek apapun. Stiker "Bisa Terbang" bisa ditempel ke Superman, Burung, atau Pesawat — mereka tidak perlu saling berhubungan.


3. Multiple Traits

Satu class bisa menggunakan banyak Trait sekaligus:

<?php
declare(strict_types=1);

trait HasTimestamps
{
    public string $createdAt;
    
    public function setCreatedAt(): void
    {
        $this->createdAt = date('Y-m-d H:i:s');
    }
}

trait SoftDeletes
{
    public ?string $deletedAt = null;

    public function softDelete(): void
    {
        $this->deletedAt = date('Y-m-d H:i:s');
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt !== null;
    }

    public function restore(): void
    {
        $this->deletedAt = null;
    }
}

trait HasSlug
{
    public string $slug;

    public function generateSlug(string $title): void
    {
        $this->slug = strtolower(
            preg_replace('/[^a-zA-Z0-9]+/', '-', $title)
        );
    }
}

// Gabungkan semua!
class Article
{
    use HasTimestamps, SoftDeletes, HasSlug;

    public function __construct(
        public string $title,
        public string $content,
    ) {
        $this->setCreatedAt();
        $this->generateSlug($title);
    }
}

$article = new Article("Belajar PHP Trait", "Konten artikel...");
echo $article->slug;        // belajar-php-trait
echo $article->isDeleted(); // false

$article->softDelete();
echo $article->isDeleted(); // true

$article->restore();
echo $article->isDeleted(); // false

4. Trait vs Interface vs Abstract Class

AspekInterfaceAbstract ClassTrait
Keywordimplementsextendsuse
IsiHanya deklarasiDeklarasi + implementasiImplementasi lengkap
Multiple✅ Banyak❌ Satu saja✅ Banyak
Property
Constructor❌ (tidak disarankan)
TujuanKontrak behaviorTemplate + logika sharedCode reuse horizontal

Kapan Pakai Apa?

"Class ini HARUS bisa melakukan X"      → Interface
"Class ini ADALAH jenis X"              → Abstract Class  
"Class ini BUTUH kemampuan X"           → Trait

5. Contoh Realistis: Sistem Blog

<?php
declare(strict_types=1);

// Interface: Kontrak
interface Publishable
{
    public function publish(): void;
    public function isPublished(): bool;
}

// Abstract class: Template
abstract class BaseModel
{
    abstract public function tableName(): string;

    public function save(): string
    {
        return "Menyimpan ke tabel: " . $this->tableName();
    }
}

// Trait: Kemampuan tambahan
trait HasAuthor
{
    public string $authorName;

    public function setAuthor(string $name): void
    {
        $this->authorName = $name;
    }

    public function getAuthor(): string
    {
        return $this->authorName;
    }
}

// 🎯 Gabungkan semuanya
class BlogPost extends BaseModel implements Publishable
{
    use HasTimestamps, SoftDeletes, HasAuthor;

    private bool $published = false;

    public function __construct(
        public string $title,
        public string $content,
    ) {
        $this->setCreatedAt();
    }

    public function tableName(): string
    {
        return 'blog_posts';
    }

    public function publish(): void
    {
        $this->published = true;
    }

    public function isPublished(): bool
    {
        return $this->published;
    }
}

:::tip Di Laravel Nanti... Beberapa Trait bawaan Laravel yang paling sering dipakai:

  • SoftDeletes — Hapus data tanpa benar-benar menghapus dari database
  • HasFactory — Membuat data dummy untuk testing
  • Notifiable — Kirim notifikasi (email, SMS, Slack)
  • HasApiTokens — Autentikasi API dengan token :::

Latihan

  1. Buat trait Loggable dengan method log(string $message): void yang menyimpan pesan ke array $logs
  2. Buat trait Cacheable dengan method cache(string $key, mixed $value): void dan getCache(string $key): mixed
  3. Buat class ProductService yang menggunakan kedua trait tersebut
  4. Buat 5 log entry dan 3 cache entry, lalu tampilkan semuanya

Selanjutnya

Sekarang kamu bisa berbagi kode antar class dengan elegan. Lanjut ke Namespace & Autoloading → untuk belajar cara mengorganisasi ratusan class tanpa konflik nama.