PHP Trait

Trait 是一種程式碼重用機制,可以在多個類別之間共享方法。Trait 解決了 PHP 單一繼承的限制。

基本用法

<?php
trait Timestampable {
    public ?DateTime $createdAt = null;
    public ?DateTime $updatedAt = null;
    
    public function touch(): void {
        $now = new DateTime();
        if ($this->createdAt === null) {
            $this->createdAt = $now;
        }
        $this->updatedAt = $now;
    }
}

class Post {
    use Timestampable;
    
    public function __construct(
        public string $title
    ) {
        $this->touch();
    }
}

class Comment {
    use Timestampable;
    
    public function __construct(
        public string $content
    ) {
        $this->touch();
    }
}

$post = new Post("Hello World");
echo $post->createdAt->format('Y-m-d');
?>

使用多個 Trait

<?php
trait Loggable {
    public function log(string $message): void {
        echo "[LOG] $message\n";
    }
}

trait Cacheable {
    private array $cache = [];
    
    public function cache(string $key, mixed $value): void {
        $this->cache[$key] = $value;
    }
    
    public function getFromCache(string $key): mixed {
        return $this->cache[$key] ?? null;
    }
}

class Service {
    use Loggable, Cacheable;
    
    public function process(): void {
        $this->log("Processing...");
        $this->cache('result', 'done');
    }
}
?>

衝突解決

當多個 Trait 有相同的方法時,需要解決衝突:

<?php
trait A {
    public function hello(): void {
        echo "Hello from A\n";
    }
}

trait B {
    public function hello(): void {
        echo "Hello from B\n";
    }
}

class MyClass {
    use A, B {
        A::hello insteadof B;  // 使用 A 的 hello
        B::hello as helloB;    // B 的 hello 改名為 helloB
    }
}

$obj = new MyClass();
$obj->hello();   // Hello from A
$obj->helloB();  // Hello from B
?>

改變存取修飾符

<?php
trait Example {
    private function secret(): void {
        echo "Secret\n";
    }
}

class MyClass {
    use Example {
        secret as public;  // 改為 public
    }
}

$obj = new MyClass();
$obj->secret();  // Secret
?>

Trait 中的抽象方法

<?php
trait Validator {
    abstract public function validate(): bool;
    
    public function isValid(): bool {
        return $this->validate();
    }
}

class User {
    use Validator;
    
    public function __construct(
        public string $email
    ) {}
    
    public function validate(): bool {
        return filter_var($this->email, FILTER_VALIDATE_EMAIL) !== false;
    }
}
?>

Trait 中的靜態成員

<?php
trait Counter {
    private static int $count = 0;
    
    public static function increment(): void {
        self::$count++;
    }
    
    public static function getCount(): int {
        return self::$count;
    }
}

class A {
    use Counter;
}

class B {
    use Counter;
}

A::increment();
A::increment();
B::increment();

echo A::getCount();  // 2
echo B::getCount();  // 1(各自獨立計數)
?>

實際範例

<?php
trait SoftDelete {
    public ?DateTime $deletedAt = null;
    
    public function delete(): void {
        $this->deletedAt = new DateTime();
    }
    
    public function restore(): void {
        $this->deletedAt = null;
    }
    
    public function isDeleted(): bool {
        return $this->deletedAt !== null;
    }
}

trait HasUuid {
    public readonly string $uuid;
    
    protected function generateUuid(): void {
        $this->uuid = sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }
}

class User {
    use SoftDelete, HasUuid;
    
    public function __construct(
        public string $name
    ) {
        $this->generateUuid();
    }
}

$user = new User("Alice");
echo $user->uuid;
$user->delete();
var_dump($user->isDeleted());  // true
?>