Traits是php5.4才加入的新語法,用意是讓物件的組織與程式的重用更靈活。
跟Java一樣,PHP不允許多重繼承,這樣可以減少物件組織的複雜性,也比較容易避開一些相依問題。但是在許多狀況下,我們可能只是需要一些特定的功能,這些功能幾乎沒有相依性,用繼承來取得太複雜,這時候通常的作法是把它做成獨立的類別,然後在需要使用的地方把物件當做property來使用(composition)。但是在實際上需要作為物件的方法才方便使用時,就不適合這樣做了。使用Traits,可以在需要某些「特定」的特性與功能時,才引用特定的Traits,這樣很直接方便。
參考:PHP: Traits - Manual
先來看看Traits的語法:
<?php
trait a {
private $name='fillano';
public function show() {
parent::show();
echo $this->name."\n";
}
}
class b {
public function show() {
echo "Hello ";
}
}
class c extends b {
use a;
}
$a = new c;
$a->show();
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-3a.php
Hello fillano
可以看出,Traits的宣告,其實和class差不多,只是引用的時候是使用"use"。如果有名稱衝突,優先順序是類別->Traits->父類別。在上例中,如果class c也定義了show()方法,那執行的就會是c的show()方法,而不是從Traits中取得的方法。而parent::show(),仍是定義在class b中的show()。
在Traits中可以定義abstract方法,使用Traits的類別,則必須實作這些方法。例如一個簡單的Template Method Pattern,就可以用Traits來達成:
<?php
trait Tag {
abstract function prefix();
abstract function postfix();
function tag($name) {
echo $this->prefix().$name.$this->postfix()."\n";
}
}
class HtmlOpenTag {
use Tag;
function prefix() {
return '<';
}
function postfix() {
return '>';
}
}
class HtmlCloseTag {
use Tag;
function prefix() {
return '</';
}
function postfix() {
return '>';
}
}
class BbcodeOpenTag {
use Tag;
function prefix() {
return '[ ';
}
function postfix() {
return ']';
}
}
class BbcodeCloseTag {
use Tag;
function prefix() {
return '[ /';
}
function postfix() {
return ']';
}
}
$c = new HtmlOpenTag;
$c->tag('div');
$d = new HtmlCloseTag;
$d->tag('div');
$e = new BbcodeOpenTag;
$e->tag('url');
$f = new BbcodeCloseTag;
$f->tag('url');
Tag trait定義了tag方法,就是在參數前後插入prefix()跟postfix()方法回傳的值。接下來定義四個類別兩個是html tag,兩個是bbcode tag,他們實作了各自的prefix()跟postfix(),然後呼叫tag()方法就可以顯示不同的tag。
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-3b.php
<div>
</div>
[ url]
[ /url]
當使用了Traits之後,從Traits得來的方法與屬性也會繼承下去:
<?php
trait a {
private $name;
function hello() {
echo "Hello ".$this->name.".\n";
}
}
class b {
use a;
function __construct($name) {
$this->name = $name;
}
}
class c extends b {
function __construct($name) {
parent::__construct($name);
}
}
$d = new c('Fillano');
$d->hello();
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-3c.php
Hello Fillano.
總之,適合定義為Traits來使用的,最好都是目的特定,而且不會使用到太多屬性與方法,也沒有太多依賴關係與副作用的功能,因為使用他其實就像多重繼承,不小心的話反而會讓物件的組織複雜化。
前一篇文章中簡單實作了一個EventEmitter,這個也很適合改成Traists:
<?php
trait EventEmitter {
private $events = array();
public function on($event, $callback) {
if(!isset($this->events[$event])) {
$this->events[$event] = array();
}
$this->events[$event][] = $callback;
}
public function emit() {
$arguments = func_get_args();
$event = array_shift($arguments);
if(isset($this->events[$event])) {
foreach($this->events[$event] as $handler) {
call_user_func_array($handler, $arguments);
}
}
}
}
class StringChuckSplit {
use EventEmitter;
private $str='';
function __construct($str) {
$this->str = $str;
}
function split($delimiter) {
$r = explode($delimiter, $this->str);
try {
foreach($r as $s) {
$this->emit('chuck', null, $s);
}
}
catch(Exception $e) {
$this->emit('chuck', $e, null);
}
}
}
function test($str, $prefix, $postfix) {
$c = new StringChuckSplit($str);
$c->on('chuck', function($err, $data) use($prefix, $postfix) {
if(!$err) {
echo $prefix.$data.$postfix."\n";
}
});
echo "<ul>\n";
$c->split(',');
echo "</ul>\n";
}
test('ab,cd,ef,g', ' <li>', '</li>');
執行的結果仍然一樣:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-3d.php
<ul>
<li>ab</li>
<li>cd</li>
<li>ef</li>
<li>g</li>
</ul>
這個太新了,用的人應該還不多
其實把它當作Class就可以,只是是透過use來使用,不是用extends來繼承。
真的是高手
PHP基本的就很好用
沒錯!
而且,寫Traits的也最好是高手。既然開了一道多重繼承的門,就要小心。我是覺得設計時應該要把它從繼承體系抽出,只給Library的Consumer使用。Traits的用意,應該是做出Duck Typing這方面的應用...
這樣用正確嗎?
<?php
trait mybase {
protected function __construct(){}
public static function iGetTotal(){
print self::$sName.PHP_EOL;
}
}
class CTest {
use mybase;
public static $sName = 'Peter';
public function __construct(){
parent::__construct();
}
}
$d = CTest::iGetTotal();
OK阿?
快速概念驗證可以直接用一些雲端服務:http://phpfiddle.org/ ,跑一下就知道了。
何時用static是另外的考量,跟trait就沒有直接關係了。trait是一種組織程式以避免程式碼重複的方法,就看你的需求來使用就好。