PHP 5.4 引入 Trait(特徵),解決**單繼承語言無法複用多組獨立方法**的問題。Trait 類似「可攜帶的方法包」,可在多個類別中 use,實現**水平程式碼復用**,避免深層繼承樹與 Mixin 混入類的菱形繼承問題。
<?php
trait Loggable {
protected $logFile = 'app.log';
public function log($message) {
file_put_contents($this->logFile, "[$this->logFile] $message\n", FILE_APPEND);
}
}
class UserService {
use Loggable; // 將 Loggable 方法/屬性「複製」進來
public function register($name) {
$this->log("Register user: $name");
// ...
}
}
$svc = new UserService();
$svc->register('Tom'); // 寫入 app.log
當前類方法 > Trait 方法 > 父類方法
<?php
trait SayWorld {
public function sayHello() { echo "Trait: Hello World!\n"; }
}
class Base {
public function sayHello() { echo "Base: Hello\n"; }
}
class MyHello extends Base {
use SayWorld;
public function sayHello() { echo "Class: Hello Universe!\n"; }
}
$o = new MyHello();
$o->sayHello(); // 輸出: Class: Hello Universe!
<?php
trait Hello { public function sayHello() { echo "Hello "; } }
trait World { public function sayWorld() { echo "World\n"; } }
class MyHelloWorld {
use Hello, World; // 同時擁有 sayHello() 和 sayWorld()
}
$o = new MyHelloWorld();
$o->sayHello(); // Hello
$o->sayWorld(); // World
<?php
trait A { public function smallTalk() { echo "a\n"; } public function bigTalk() { echo "A\n"; } }
trait B { public function smallTalk() { echo "b\n"; } public function bigTalk() { echo "B\n"; } }
class Talker {
use A, B {
B::smallTalk insteadof A; // 選 B 的 smallTalk,排除 A 的
A::bigTalk insteadof B; // 選 A 的 bigTalk,排除 B 的
}
}
$o = new Talker();
$o->smallTalk(); // b
$o->bigTalk(); // A
<?php
class Talker2 {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as bTalk; // 保留 B::bigTalk 並命名為 bTalk
}
}
$o = new Talker2();
$o->bigTalk(); // A
$o->bTalk(); // B
<?php
trait HelloRuesin {
public function sayHello() { echo "Hello Ruesin\n"; }
}
class Hello {
use HelloRuesin {
sayHello as protected; // 將 public 改為 protected
}
}
class Ruesin {
use HelloRuesin {
sayHello as private sayHellos; // 產生私有別名 sayHellos,原 sayHello 仍為 public
}
}
$o = new Hello();
// $o->sayHello(); // Fatal: protected
$r = new Ruesin();
$r->sayHello(); // Hello Ruesin (public)
$r->sayHellos(); // Fatal: private
<?php
trait Hello { public function sayHello() { echo "Hello"; } }
trait Ruesin { public function sayRuesin() { echo "Ruesin"; } }
trait HelloRuesin {
use Hello, Ruesin; // 組合成新 Trait
}
class SayHelloRuesin {
use HelloRuesin;
}
$o = new SayHelloRuesin();
$o->sayHello(); // Hello
$o->sayRuesin(); // Ruesin
<?php
trait Hello {
public function sayHelloRuesin() {
echo "Hello " . $this->getName() . "\n";
}
abstract public function getName(); // 強制使用類實作
}
class HelloRuesin {
use Hello;
private $name;
public function __construct($name) { $this->name = $name; }
public function getName() { return $this->name; } // 必須實作
}
(new HelloRuesin('Ruesin'))->sayHelloRuesin(); // Hello Ruesin
<?php
trait Counter {
public function inc() {
static $c = 0; // 函數級靜態變數,所有使用該 Trait 的類**共享同一個** $c
echo ++$c . "\n";
}
public static function whoami() {
echo __CLASS__ . "\n"; // __CLASS__ 解析為使用類
}
}
class C1 { use Counter; }
class C2 { use Counter; }
$c1 = new C1(); $c1->inc(); // 1
$c2 = new C2(); $c2->inc(); // 2 (共享!)
C1::whoami(); // C1
C2::whoami(); // C2
⚠️ **注意**:Trait 中 static $var 屬於 **函數靜態變數**,**所有使用該 Trait 的類共享同一份**,而非各類別獨立。若需類別級獨立靜態屬性,請在類別中自行定義 protected static $counter = 0;。
<?php
trait PropertiesTrait {
public $x = 1;
}
class Example {
use PropertiesTrait;
// public $x = 2; // Fatal: 衝突(初始值不同)
// public $x = 1; // E_STRICT: 相容但重複
// protected $x = 1; // Fatal: 可見性不同
}
$e = new Example();
echo $e->x; // 1
| 特性 | Trait | 介面 | 抽象類 | 繼承 |
|---|---|---|---|---|
| 程式碼復用 | ✅ 方法/屬性實作 | ❌ 僅契約 | ✅ 部分實作 | ✅ 完整實作 |
| 多重組合 | ✅ use 多個 | ✅ implements 多個 | ❌ 單繼承 | ❌ 單繼承 |
| 可實例化 | ❌ | ❌ | ❌ | ✅ |
| 構造函數 | ✅ 可定義 __construct | ❌ | ✅ | ✅ |
| 靜態成員 | ⚠️ 函數級共享 | ❌ | ✅ 類級獨立 | ✅ 類級獨立 |
| 適用場景 | 跨層級橫向功能(日誌、快取、序列化、權限檢查) | 定義契約、多型 | 共享核心邏輯、定義模板方法 | is-a 關係、垂直擴充 |
<?php
trait CacheableRepository {
public function getCached($key, callable $fallback, $ttl = 300) {
$cacheKey = "repo:{$key}";
if ($this->cache->has($cacheKey)) return $this->cache->get($cacheKey);
$data = $fallback();
$this->cache->set($cacheKey, $data, $ttl);
return $data;
}
}
trait EventfulRepository {
protected function emit($event, $payload) {
$this->dispatcher->dispatch(new $event($payload));
}
}
class UserRepository {
use CacheableRepository, EventfulRepository;
// 同時擁有快取與事件發射能力,無需繼承共同父類
}
insteadof/as 顯式解決。$this->property。@property、@method 註解標註 Trait 要求/提供的屬性方法,IDE 才能提示。new class { use TargetTrait; },隔離驗證 Trait 邏輯。Trait 是 PHP 解決「單繼承下橫向復用」的利器。掌握 **優先順序**、**insteadof/as 衝突解決**、**抽象方法契約**、**靜態成員共享陷阱**、**屬性相容性規則**,並遵循「無狀態、橫切關注點、顯式解決衝突」原則,即能安全、優雅地在專案中大規模應用 Trait。