iT邦幫忙

DAY 4
12

逐步提昇PHP技術能力系列 第 4

逐步提昇PHP技術能力 - PHP的語言特性 : Traits

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>

上一篇
逐步提昇PHP技術能力 - PHP的語言特性 : 匿名函數 / Closure / Callable
下一篇
逐步提昇PHP技術能力 - PHP的語言特性 : Generators / Iterators
系列文
逐步提昇PHP技術能力30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
SunAllen
iT邦研究生 1 級 ‧ 2013-10-04 00:42:06

沙發

完全看不懂啊XD...Orz

0
fillano
iT邦超人 1 級 ‧ 2013-10-04 06:31:13

這個太新了,用的人應該還不多XD

其實把它當作Class就可以,只是是透過use來使用,不是用extends來繼承。

0
ak02
iT邦研究生 1 級 ‧ 2013-10-07 09:34:46

真的是高手
PHP基本的就很好用

fillano iT邦超人 1 級 ‧ 2013-10-07 10:14:36 檢舉

沒錯!

而且,寫Traits的也最好是高手。既然開了一道多重繼承的門,就要小心。我是覺得設計時應該要把它從繼承體系抽出,只給Library的Consumer使用。Traits的用意,應該是做出Duck Typing這方面的應用...

0
永往直前
iT邦新手 4 級 ‧ 2020-03-18 04:32:48

很棒的分享,謝謝

0
yehchge0968
iT邦新手 5 級 ‧ 2020-12-16 14:10:08

這樣用正確嗎?

<?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();

fillano iT邦超人 1 級 ‧ 2020-12-17 14:00:16 檢舉

OK阿?

快速概念驗證可以直接用一些雲端服務:http://phpfiddle.org/ ,跑一下就知道了。

何時用static是另外的考量,跟trait就沒有直接關係了。trait是一種組織程式以避免程式碼重複的方法,就看你的需求來使用就好。

我要留言

立即登入留言