🏠 Home / Hub

🐘 PHP Lesson 11 — Traits

← Back to PHP Menu  |  🏠 Hub

1. Trait ဆိုတာ ဘာလဲ?

Trait = reusable code block — class ထဲ copy-paste လုပ်ပေးသလို ထည့်နိုင်
PHP မှာ class တစ်ခု parent class တစ်ခုပဲ extends နိုင် — Trait ကတော့ အများကြီး use နိုင်
"Horizontal reuse" — related မဟုတ်သော classes တွေမှာ same behavior ထည့်ဖို့
<?php

// Trait definition
trait Timestampable {
    private ?string $createdAt = null;
    private ?string $updatedAt = null;

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

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

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

trait SoftDeletable {
    private ?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 Serializable {
    public function toArray(): array {
        return get_object_vars($this);
    }

    public function toJson(): string {
        return json_encode($this->toArray());
    }
}

// Use multiple traits
class Post {
    use Timestampable, SoftDeletable, Serializable;

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

$post = new Post("Hello World", "My first post");
echo $post->getCreatedAt();  // "2024-01-15 10:30:00"
$post->softDelete();
echo $post->isDeleted();     // true
$post->restore();
echo $post->isDeleted();     // false
echo $post->toJson();        // JSON of all properties

2. Trait with Abstract Method

<?php

trait Validatable {
    // Abstract method in trait — using class MUST implement
    abstract protected function rules(): array;

    public function validate(array $data): array {
        $errors = [];
        foreach ($this->rules() as $field => $rule) {
            if ($rule === 'required' && empty($data[$field])) {
                $errors[$field] = "$field is required";
            }
            if (str_starts_with($rule, 'min:')) {
                $min = (int) substr($rule, 4);
                if (strlen($data[$field] ?? '') < $min) {
                    $errors[$field] = "$field must be at least $min characters";
                }
            }
            if ($rule === 'email' && !filter_var($data[$field] ?? '', FILTER_VALIDATE_EMAIL)) {
                $errors[$field] = "$field must be a valid email";
            }
        }
        return $errors;
    }

    public function isValid(array $data): bool {
        return empty($this->validate($data));
    }
}

class UserForm {
    use Validatable;

    protected function rules(): array {
        return [
            'name'  => 'required',
            'email' => 'email',
            'password' => 'min:8',
        ];
    }
}

$form = new UserForm();
$errors = $form->validate([
    'name'  => 'Ko Ko',
    'email' => 'invalid-email',
    'password' => '123',
]);
print_r($errors);
// ['email' => '...must be a valid email', 'password' => '...at least 8 characters']

3. Trait Conflict Resolution

<?php

trait A {
    public function hello(): string { return "Hello from A"; }
    public function hi(): string    { return "Hi from A"; }
}

trait B {
    public function hello(): string { return "Hello from B"; }
    public function hi(): string    { return "Hi from B"; }
}

class MyClass {
    use A, B {
        // Conflict resolution — specify which to use
        A::hello insteadof B;   // use A's hello(), ignore B's
        B::hi    insteadof A;   // use B's hi(), ignore A's

        // Alias — keep both with different names
        B::hello as helloFromB;
        A::hi    as hiFromA;
    }
}

$obj = new MyClass();
echo $obj->hello();      // "Hello from A"  (A wins)
echo $obj->hi();         // "Hi from B"     (B wins)
echo $obj->helloFromB(); // "Hello from B"  (alias)
echo $obj->hiFromA();    // "Hi from A"     (alias)

4. Real-World Trait Examples

<?php

// Logger trait
trait Loggable {
    private array $logs = [];

    public function log(string $level, string $message): void {
        $this->logs[] = [
            'level'   => strtoupper($level),
            'message' => $message,
            'time'    => date('H:i:s'),
        ];
    }

    public function getLogs(): array { return $this->logs; }
    public function info(string $msg): void  { $this->log('info', $msg); }
    public function error(string $msg): void { $this->log('error', $msg); }
}

// Pagination trait
trait Paginatable {
    protected int $perPage = 10;

    public function paginate(array $items, int $page = 1): array {
        $total   = count($items);
        $offset  = ($page - 1) * $this->perPage;
        $data    = array_slice($items, $offset, $this->perPage);

        return [
            'data'         => $data,
            'total'        => $total,
            'per_page'     => $this->perPage,
            'current_page' => $page,
            'last_page'    => (int) ceil($total / $this->perPage),
        ];
    }
}

// Singleton trait
trait SingletonTrait {
    private static ?self $instance = null;

    private function __construct() {}

    public static function getInstance(): static {
        if (static::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }
}

// Usage
class OrderService {
    use Loggable, Paginatable, SingletonTrait;

    public function processOrder(int $orderId): void {
        $this->info("Processing order #$orderId");
        // ...
        $this->info("Order #$orderId processed successfully");
    }
}

$service = OrderService::getInstance();
$service->processOrder(42);
print_r($service->getLogs());
💡 Laravel uses Traits extensively: SoftDeletes, HasFactory, Notifiable, HasApiTokens

← PHP 10  |  Next: PHP 12 → Namespaces & Composer →

📌 Study Checklist