PHP Conference Japan 2024

Traits

PHP 實作了一種稱為 Traits 的程式碼重複使用方法。

Traits 是單一繼承語言(如 PHP)中程式碼重複使用的一種機制。Trait 的目的是透過讓開發人員能夠在不同類別階層中的數個獨立類別中自由重複使用方法集,來減少單一繼承的一些限制。Traits 和類別組合的語意定義方式降低了複雜性,並避免了與多重繼承和 Mixins 相關的典型問題。

Trait 類似於類別,但僅用於以精細且一致的方式將功能分組。無法單獨實例化 Trait。它是傳統繼承的補充,並啟用行為的水平組合;也就是說,應用類別成員而無需繼承。

範例 #1 Trait 範例

<?php
trait ezcReflectionReturnInfo {
function
getReturnType() { /*1*/ }
function
getReturnDescription() { /*2*/ }
}

class
ezcReflectionMethod extends ReflectionMethod {
use
ezcReflectionReturnInfo;
/* ... */
}

class
ezcReflectionFunction extends ReflectionFunction {
use
ezcReflectionReturnInfo;
/* ... */
}
?>

優先順序

基底類別繼承的成員會被 Trait 插入的成員覆寫。優先順序為目前類別的成員覆寫 Trait 方法,而 Trait 方法又覆寫繼承的方法。

範例 #2 優先順序範例

從基底類別繼承的方法會被從 SayWorld Trait 插入 MyHelloWorld 的方法覆寫。對於 MyHelloWorld 類別中定義的方法,行為也是相同的。優先順序為目前類別的方法覆寫 Trait 方法,而 Trait 方法又覆寫基底類別的方法。

<?php
class Base {
public function
sayHello() {
echo
'Hello ';
}
}

trait
SayWorld {
public function
sayHello() {
parent::sayHello();
echo
'World!';
}
}

class
MyHelloWorld extends Base {
use
SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

上述範例會輸出

Hello World!

範例 #3 替代的優先順序範例

<?php
trait HelloWorld {
public function
sayHello() {
echo
'Hello World!';
}
}

class
TheWorldIsNotEnough {
use
HelloWorld;
public function
sayHello() {
echo
'Hello Universe!';
}
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

上述範例會輸出

Hello Universe!

多個 Traits

可以透過在 use 陳述式中列出多個 Traits(以逗號分隔),將它們插入類別中。

範例 #4 多個 Traits 用法

<?php
trait Hello {
public function
sayHello() {
echo
'Hello ';
}
}

trait
World {
public function
sayWorld() {
echo
'World';
}
}

class
MyHelloWorld {
use
Hello, World;
public function
sayExclamationMark() {
echo
'!';
}
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

上述範例會輸出

Hello World!

衝突解決

如果兩個 Traits 插入名稱相同的方法,則會產生嚴重錯誤,除非明確解決衝突。

若要解決在相同類別中使用的 Traits 之間的命名衝突,需要使用 insteadof 運算子來選擇衝突方法中的其中一個。

由於這只允許排除方法,因此可以使用 as 運算子來為其中一個方法新增別名。請注意,as 運算子不會重新命名方法,也不會影響任何其他方法。

範例 #5 衝突解決

在此範例中,Talker 使用了 Traits A 和 B。由於 A 和 B 有衝突的方法,因此它定義要使用來自 Trait B 的 smallTalk 變體,以及來自 Trait A 的 bigTalk 變體。

Aliased_Talker 利用 as 運算子,以便能夠在額外的別名 talk 下使用 B 的 bigTalk 實作。

<?php
trait A {
public function
smallTalk() {
echo
'a';
}
public function
bigTalk() {
echo
'A';
}
}

trait
B {
public function
smallTalk() {
echo
'b';
}
public function
bigTalk() {
echo
'B';
}
}

class
Talker {
use
A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}

class
Aliased_Talker {
use
A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>

變更方法可見性

使用 as 語法,也可以調整在展現類別中方法的可見性。

範例 #6 變更方法可見性

<?php
trait HelloWorld {
public function
sayHello() {
echo
'Hello World!';
}
}

// 變更 sayHello 的可見性
class MyClass1 {
use
HelloWorld { sayHello as protected; }
}

// 使用變更的可見性來別名方法
// sayHello 的可見性不變
class MyClass2 {
use
HelloWorld { sayHello as private myPrivateHello; }
}
?>

由 Traits 組成的 Traits

如同類別可以使用 traits 一樣,其他的 traits 也可以使用。透過在 trait 定義中使用一個或多個 traits,它可以部分或完全由那些其他 traits 中定義的成員組成。

範例 #7 由 Traits 組成的 Traits

<?php
trait Hello {
public function
sayHello() {
echo
'Hello ';
}
}

trait
World {
public function
sayWorld() {
echo
'World!';
}
}

trait
HelloWorld {
use
Hello, World;
}

class
MyHelloWorld {
use
HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

上述範例會輸出

Hello World!

抽象 Trait 成員

Traits 支援使用抽象方法,以便對展現的類別施加需求。支援 public、protected 和 private 方法。在 PHP 8.0.0 之前,僅支援 public 和 protected 的抽象方法。

注意

自 PHP 8.0.0 起,具體方法的簽名必須遵循簽名相容性規則。以前,它的簽名可能不同。

範例 #8 通過抽象方法表達需求

<?php
trait Hello {
public function
sayHelloWorld() {
echo
'Hello'.$this->getWorld();
}
abstract public function
getWorld();
}

class
MyHelloWorld {
private
$world;
use
Hello;
public function
getWorld() {
return
$this->world;
}
public function
setWorld($val) {
$this->world = $val;
}
}
?>

靜態 Trait 成員

Traits 可以定義靜態變數、靜態方法和靜態屬性。

注意:

自 PHP 8.1.0 起,直接在 trait 上呼叫靜態方法或存取靜態屬性已被棄用。靜態方法和屬性應僅在類別上使用 trait 存取。

範例 #9 靜態變數

<?php
trait Counter {
public function
inc() {
static
$c = 0;
$c = $c + 1;
echo
"$c\n";
}
}

class
C1 {
use
Counter;
}

class
C2 {
use
Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

範例 #10 靜態方法

<?php
trait StaticExample {
public static function
doSomething() {
return
'Doing something';
}
}

class
Example {
use
StaticExample;
}

Example::doSomething();
?>

範例 #11 靜態屬性

注意

在 PHP 8.3.0 之前,靜態屬性會在所有使用該 trait 的類別之間共享。自 PHP 8.3.0 起,每個使用該 trait 的類別都有其自己的靜態屬性副本。

<?php
trait StaticExample {
public static
$static = 'foo';
}

class
Example {
use
StaticExample;
}

echo
Example::$static;
?>

屬性

Traits 也可以定義屬性。

範例 #12 定義屬性

<?php
trait PropertiesTrait {
public
$x = 1;
}

class
PropertiesExample {
use
PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;
?>

如果一個 trait 定義了一個屬性,那麼類別就不能定義一個同名的屬性,除非它是相容的(相同的可見性和類型、readonly 修飾符和初始值),否則會發出致命錯誤。

範例 #13 衝突解決

<?php
trait PropertiesTrait {
public
$same = true;
public
$different1 = false;
public
bool $different2;
public
bool $different3;
}

class
PropertiesExample {
use
PropertiesTrait;
public
$same = true;
public
$different1 = true; // 致命錯誤
public string $different2; // 致命錯誤
readonly protected bool $different3; // 致命錯誤
}
?>

常數

從 PHP 8.2.0 開始,Traits 也可以定義常數。

範例 #14 定義常數

<?php
trait ConstantsTrait {
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample {
use
ConstantsTrait;
}

$example = new ConstantsExample;
echo
$example::FLAG_MUTABLE; // 1
?>

如果一個 trait 定義了一個常數,那麼類別不能定義一個具有相同名稱的常數,除非它是相容的(相同的可見性、初始值和最終性),否則會發出致命錯誤。

範例 #15 衝突解決

<?php
trait ConstantsTrait {
public const
FLAG_MUTABLE = 1;
final public const
FLAG_IMMUTABLE = 5;
}

class
ConstantsExample {
use
ConstantsTrait;
public const
FLAG_IMMUTABLE = 5; // 致命錯誤
}
?>

Final 方法

從 PHP 8.3.0 開始,final 修飾詞可以應用於來自 trait 的方法。

範例 #16 將來自 trait 的方法定義為 final

<?php
trait ConstantsTrait {
public function
method() {
echo
'Hello';
}
}

class
ConstantsExample {
use
ConstantsTrait;

final public function
method() {
echo
'Hello World';
}
}
?>
新增註解

使用者貢獻的註解 25 條註解

Safak Ozpinar / safakozpinar at gmail
12 年前
與繼承不同;如果 trait 具有靜態屬性,則每個使用該 trait 的類別都具有這些屬性的獨立實例。

使用父類別的範例
<?php
class TestClass {
public static
$_bar;
}
class
Foo1 extends TestClass { }
class
Foo2 extends TestClass { }
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // 列印:World World
?>

使用 trait 的範例
<?php
trait TestTrait {
public static
$_bar;
}
class
Foo1 {
use
TestTrait;
}
class
Foo2 {
use
TestTrait;
}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // 列印:Hello World
?>
greywire at gmail dot com
12 年前
理解 traits 是什麼以及如何使用它們的最好方法是將它們視為本質上的東西:語言輔助的複製貼上。

如果你可以將程式碼從一個類別複製貼上到另一個類別(我們都做過,即使我們盡量避免這樣做,因為它是程式碼重複),那麼你就有了 trait 的候選者。
Stefan W
11 年前
請注意,trait 的 "use" 運算子(在類別內部)和命名空間的 "use" 運算子(在類別外部)解析名稱的方式不同。命名空間的 "use" 始終將其參數視為絕對的(從全域命名空間開始)

<?php
namespace Foo\Bar;
use
Foo\Test; // 表示 \Foo\Test - 最初的 \ 是可選的
?>

另一方面,trait 的 "use" 會尊重目前的命名空間

<?php
namespace Foo\Bar;
class
SomeClass {
use
Foo\Test; // 表示 \Foo\Bar\Foo\Test
}
?>

連同閉包的 "use",現在有三個不同的 "use" 運算子。它們都表示不同的含義並且行為不同。
JustAddingSomeAdditionalUseCase
1 年前
我還沒看過這種特定的用例

「想要保留父類別方法的操作,trait 的方法呼叫 ::parent & 還有子類別方法操作」。

// 子類別。
use SuperTrait {
initialize as initializeOr;
}
public function initialize(array &$element) {
...
$this->initializeOr($element);
}
// Trait。
public function initialize(array &$element) {
...
parent::initialize($element);
}
// 父類別。
public function initialize(array &$element) {
...
}
t8 at AT pobox dot com
12 年前
traits 與繼承的另一個區別是,在 trait 中定義的方法可以存取它們使用的類別的方法和屬性,包括私有方法和屬性。

例如
<?php
trait MyTrait
{
protected function
accessVar()
{
return
$this->var;
}

}

class
TraitUser
{
use
MyTrait;

private
$var = 'var';

public function
getVar()
{
return
$this->accessVar();
}
}

$t = new TraitUser();
echo
$t->getVar(); // -> 'var'

?>
chris dot rutledge at gmail dot com
12 年前
這裡可能值得注意的是,魔術常數 __CLASS__ 變得更加神奇 - __CLASS__ 將返回使用 trait 的類別的名稱。

例如

<?php
trait sayWhere {
public function
whereAmI() {
echo
__CLASS__;
}
}

class
Hello {
use
sayWHere;
}

class
World {
use
sayWHere;
}

$a = new Hello;
$a->whereAmI(); //Hello

$b = new World;
$b->whereAmI(); //World
?>

魔術常數 __TRAIT__ 會提供你 trait 的名稱
qeremy (!) gmail
9 年前
請記住;直接使用 trait 時,「final」關鍵字在 trait 中是沒有用的,不像擴展類別/抽象類別。

<?php
trait Foo {
final public function
hello($s) { print "$s, hello!"; }
}
class
Bar {
use
Foo;
// 覆寫,沒有錯誤
final public function hello($s) { print "hello, $s!"; }
}

abstract class
Foo {
final public function
hello($s) { print "$s, hello!"; }
}
class
Bar extends Foo {
// 致命錯誤:無法在 .. 中覆寫 final 方法 Foo::hello()
final public function hello($s) { print "hello, $s!"; }
}
?>

但是這種方式會如預期般將 trait 方法設為 final;

<?php
trait FooTrait {
final public function
hello($s) { print "$s, hello!"; }
}
abstract class
Foo {
use
FooTrait;
}
class
Bar extends Foo {
// 致命錯誤:無法在 .. 中覆寫 final 方法 Foo::hello()
final public function hello($s) { print "hello, $s!"; }
}
?>
yeu_ym at yahoo dot com
5 年前
這是一個如何處理可見性和衝突的範例。

<?php

trait A
{
private function
smallTalk()
{
echo
'a';
}

private function
bigTalk()
{
echo
'A';
}
}

trait
B
{
private function
smallTalk()
{
echo
'b';
}

private function
bigTalk()
{
echo
'B';
}
}

trait
C
{
public function
smallTalk()
{
echo
'c';
}

public function
bigTalk()
{
echo
'C';
}
}

class
Talker
{
use
A, B, C {
//用於解決衝突的方法的可見性
B::smallTalk as public;
A::bigTalk as public;

//衝突解決
B::smallTalk insteadof A, C;
A::bigTalk insteadof B, C;

//使用可見性變更的別名
B::bigTalk as public Btalk;
A::smallTalk as public asmalltalk;

//僅別名,方法已定義為 public
C::bigTalk as Ctalk;
C::smallTalk as cmallstalk;
}

}

(new
Talker)->bigTalk();//A
(new Talker)->Btalk();//B
(new Talker)->Ctalk();//C

(new Talker)->asmalltalk();//a
(new Talker)->smallTalk();//b
(new Talker)->cmallstalk();//c
canufrank
8 年前
許多筆記對 trait 的行為做出了不正確的斷言,因為它們沒有擴展該類別。

因此,雖然「與繼承不同;如果 trait 具有靜態屬性,則使用該 trait 的每個類別都擁有這些屬性的獨立實例。」

使用父類別的範例
<?php
class TestClass {
public static
$_bar;
}
class
Foo1 extends TestClass { }
class
Foo2 extends TestClass { }
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // 列印:World World
?>

使用 trait 的範例
<?php
trait TestTrait {
public static
$_bar;
}
class
Foo1 {
use
TestTrait;
}
class
Foo2 {
use
TestTrait;
}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // 輸出:Hello World
?>"

顯示了一個正確的範例,只需添加
<?php
require_once('above');
class
Foo3 extends Foo2 {
}
Foo3::$_bar = 'news';
echo
Foo1::$_bar . ' ' . Foo2::$_bar . ' ' . Foo3::$_bar;

// 輸出:Hello news news

我認為一個整合過的 trait 的最佳概念模型是文字的高級插入,或者如同 某人所說的 「語言輔助的複製和貼上」。如果 Foo1 Foo2 是用 $_bar 定義的你不會期望它們共享該實例類似地你會期望 Foo3 與 Foo2 共享,而事實也確實如此

以這種方式看待可以解釋上面觀察到許多關於 「怪異」行為的 final 或 後續宣告的 private 變數
rawsrc
6 年前
關於 (Safak Ozpinar / safakozpinar at gmail) 的精彩筆記,你仍然可以使用這種方法使用 trait 來實現與繼承相同的行為
<?php

trait TestTrait {
public static
$_bar;
}

class
FooBar {
use
TestTrait;
}

class
Foo1 extends FooBar {

}
class
Foo2 extends FooBar {

}
Foo1::$_bar = 'Hello';
Foo2::$_bar = 'World';
echo
Foo1::$_bar . ' ' . Foo2::$_bar; // 輸出:World World
balbuf
8 年前
(這點已經提過了,但為了方便搜尋「relative」這個詞...)

在類別中使用 `use` 關鍵字導入 trait 時,會以目前的命名空間為基準解析相對路徑,因此應該包含一個前導斜線來表示完整路徑;而 `use` 在命名空間層級使用時,則永遠是絕對路徑。
marko at newvibrations dot net
8 年前
如同前面所述,trait 中的靜態屬性和方法可以直接使用 trait 名稱存取。由於 trait 是語言輔助的複製/貼上機制,您應該留意 trait 中的靜態屬性會在類別宣告時被初始化為 trait 屬性的值。

範例

<?php

trait Beer {
protected static
$type = 'Light';
public static function
printed(){
echo static::
$type.PHP_EOL;
}
public static function
setType($type){
static::
$type = $type;
}
}

class
Ale {
use
Beer;
}

Beer::setType("Dark");

class
Lager {
use
Beer;
}

Beer::setType("Amber");

header("Content-type: text/plain");

Beer::printed(); // Prints: Amber
Ale::printed(); // Prints: Light
Lager::printed(); // Prints: Dark

?>
qschuler at neosyne dot com
10 年前
請注意,您可以透過從一個 trait 中排除某個方法,並在另一個 trait 中以相反的方式執行相同的操作,來省略該方法的包含。

<?php

trait A {
public function
sayHello()
{
echo
'Hello from A';
}

public function
sayWorld()
{
echo
'World from A';
}
}

trait
B {
public function
sayHello()
{
echo
'Hello from B';
}

public function
sayWorld()
{
echo
'World from B';
}
}

class
Talker {
use
A, B {
A::sayHello insteadof B;
A::sayWorld insteadof B;
B::sayWorld insteadof A;
}
}

$talker = new Talker();
$talker->sayHello();
$talker->sayWorld();

?>

方法 sayHello 被導入,但方法 sayWorld 只是被排除。
Edward
12 年前
Traits 和多重繼承之間的差異在於繼承的部分。trait 並非從其他地方繼承,而是被包含或混合進來,從而成為「這個類別」的一部分。Traits 也提供了更受控制的方式來解決在使用多重繼承時(在少數支援多重繼承的語言如 C++ 中)不可避免會發生的衝突。大多數現代語言都採用「traits」或「mixin」風格的系統,而不是多重繼承,這主要是因為當一個方法在多個「混合進來」的類別中宣告時,它們能夠控制歧義。

此外,在多重繼承中無法「繼承」靜態成員函式。
ryan at derokorian dot com
12 年前
簡單的單例模式 trait。

<?php

trait singleton {
/**
* private construct, generally defined by using class
*/
//private function __construct() {}

public static function getInstance() {
static
$_instance = NULL;
$class = __CLASS__;
return
$_instance ?: $_instance = new $class;
}

public function
__clone() {
trigger_error('Cloning '.__CLASS__.' is not allowed.',E_USER_ERROR);
}

public function
__wakeup() {
trigger_error('Unserializing '.__CLASS__.' is not allowed.',E_USER_ERROR);
}
}

/**
* Example Usage
*/

class foo {
use
singleton;

private function
__construct() {
$this->name = 'foo';
}
}

class
bar {
use
singleton;

private function
__construct() {
$this->name = 'bar';
}
}

$foo = foo::getInstance();
echo
$foo->name;

$bar = bar::getInstance();
echo
$bar->name;
katrinaelaine6 at gmail dot com
7 年前
補充「atorich at gmail dot com」的內容

當你理解 trait 和延遲靜態綁定時(https://php.dev.org.tw/manual/en/language.oop5.late-static-bindings.php),魔術常數 `__CLASS__` 在 trait 中的行為會如預期般運作。

<?php

$format
= 'Class: %-13s | get_class(): %-13s | get_called_class(): %-13s%s';

trait
TestTrait {
public function
testMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
testStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

trait
DuplicateTrait {
public function
duplMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
duplStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

abstract class
AbstractClass {

use
DuplicateTrait;

public function
absMethod() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}

public static function
absStatic() {
global
$format;
printf($format, __CLASS__, get_class(), get_called_class(), PHP_EOL);
}
}

class
BaseClass extends AbstractClass {
use
TestTrait;
}

class
TestClass extends BaseClass { }

$t = new TestClass();

$t->testMethod();
TestClass::testStatic();

$t->absMethod();
TestClass::absStatic();

$t->duplMethod();
TestClass::duplStatic();

?>

將會輸出

Class: BaseClass | get_class(): BaseClass | get_called_class(): TestClass
Class: BaseClass | get_class(): BaseClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass
Class: AbstractClass | get_class(): AbstractClass | get_called_class(): TestClass

由於 Traits 被視為程式碼的字面「複製/貼上」,很明顯 `DuplicateTrait` 中定義的方法會給出與 `AbstractClass` 中定義的方法相同的結果。
D. Marti
12 年前
當您希望以不同的方式處理(篩選、排序等)相同的數據時,Traits 對於策略很有用。

舉例來說,您有一個產品列表,您想根據某些條件(品牌、規格等)篩選,或依不同方式(價格、標籤等)排序。您可以建立一個排序 trait,其中包含針對不同排序類型(數值、字串、日期等)的不同函式。然後,您不僅可以在您的產品類別中使用此 trait(如範例所示),也可以在其他需要類似策略的類別中使用(例如對某些資料應用數值排序)。

<?php
trait SortStrategy {
private
$sort_field = null;
private function
string_asc($item1, $item2) {
return
strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
}
private function
string_desc($item1, $item2) {
return
strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
}
private function
num_asc($item1, $item2) {
if (
$item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return (
$item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
}
private function
num_desc($item1, $item2) {
if (
$item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
return (
$item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
}
private function
date_asc($item1, $item2) {
$date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
$date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
if (
$date1 == $date2) return 0;
return (
$date1 < $date2 ? -1 : 1 );
}
private function
date_desc($item1, $item2) {
$date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
$date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
if (
$date1 == $date2) return 0;
return (
$date1 > $date2 ? -1 : 1 );
}
}

class
Product {
public
$data = array();

use
SortStrategy;

public function
get() {
// do something to get the data, for this ex. I just included an array
$this->data = array(
101222 => array('label' => '超讚產品', 'price' => 10.50, 'date_added' => '2012-02-01'),
101232 => array('label' => '沒那麼讚的產品', 'price' => 5.20, 'date_added' => '2012-03-20'),
101241 => array('label' => '蠻棒的產品', 'price' => 9.65, 'date_added' => '2012-04-15'),
101256 => array('label' => '超酷的產品', 'price' => 12.55, 'date_added' => '2012-01-11'),
101219 => array('label' => '普通產品', 'price' => 3.69, 'date_added' => '2012-06-11'),
);
}

public function
sort_by($by = 'price', $type = 'asc') {
if (!
preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
switch (
$by) {
case
'name':
$this->sort_field = 'label';
uasort($this->data, array('Product', 'string_'.$type));
break;
case
'date':
$this->sort_field = 'date_added';
uasort($this->data, array('Product', 'date_'.$type));
break;
default:
$this->sort_field = 'price';
uasort($this->data, array('Product', 'num_'.$type));
}
}
}

$product = new Product();
$product->get();
$product->sort_by('name');
echo
'<pre>'.print_r($product->data, true).'</pre>';
?>
bscheshirwork at gmail dot com
7 年前
https://3v4l.org/mFuQE

1. 如果從 trait 取得與類別同名的方法,則不會棄用
2. 在 C 中將同名方法 ba 替換為 aa

trait ATrait {
public function a(){
return 'Aa';
}
}

trait BTrait {
public function a(){
return 'Ba';
}
}

class C {
use ATrait{
a as aa;
}
use BTrait{
a as ba;
}

public function a() {
return static::aa() . static::ba();
}
}

$o = new C;
echo $o->a(), "\n";

class D {
use ATrait{
ATrait::a as aa;
}
use BTrait{
BTrait::a as ba;
}

public function a() {
return static::aa() . static::ba();
}
}

$o = new D;
echo $o->a(), "\n";

class E {
use ATrait{
ATrait::a as aa;
ATrait::a insteadof BTrait;
}
use BTrait{
BTrait::a as ba;
}

public function e() {
return static::aa() . static::ba();
}
}

$o = new E;
echo $o->e(), "\n";

class F {
use ATrait{
a as aa;
}
use BTrait{
a as ba;
}

public function f() {
return static::aa() . static::ba();
}
}

$o = new F;
echo $o->f(), "\n";

AaAa
AaBa

Deprecated: 與其類別同名的方法在未來的 PHP 版本中將不再是建構子;E 在 /in/mFuQE 的第 48 行有一個被棄用的建構子
AaBa

Fatal error: Trait 方法 a 未被應用,因為 F 在 /in/mFuQE 的第 65 行與其他 trait 方法發生衝突
Kristof
10 年前
別忘了你也可以建立複雜(嵌入式)的 traits

<?php
trait Name {
// ...
}
trait
Address {
// ...
}
trait
Telephone {
// ...
}
trait
Contact {
use
Name, Address, Telephone;
}
class
Customer {
use
Contact;
}
class
Invoce {
use
Contact;
}
?>
Carlos Alberto Bertholdo Carucce
8 年前
如果你想要解決名稱衝突,並且也想變更 trait 方法的可見性,你需要將兩者在同一行宣告。

trait testTrait{

public function test(){
echo 'trait test';
}

}

class myClass{

use testTrait {
testTrait::test as private testTraitF;
}

public function test(){
echo 'class test';
echo '<br/>';
$this->testTraitF();
}

}

$obj = new myClass();
$obj->test(); // 輸出 'trait test' 和 'class test'
$obj->testTraitF(); // 此方法無法存取 (致命錯誤:呼叫私有方法 myClass::testTraitF())
Oddant
11 年前
我想很明顯可以看出,使用 'use' 加上 traits 的名稱,必須被視為僅僅是將程式碼行複製/貼上到它們被使用的地方。
guidobelluomo at gmail dot com
4 年前
如果你覆寫一個由 trait 定義的方法,呼叫父方法也會呼叫 trait 的覆寫。因此,如果你需要繼承一個擁有 trait 的類別,你可以擴展該類別而不會失去 trait 的功能。

<?php

trait ExampleTrait
{
public function
output()
{
parent::output();
echo
"bar<br>";
}
}

class
Foo
{
public function
output()
{
echo
"foo<br>";
}
}

class
FooBar extends Foo
{
use
ExampleTrait;
}

class
FooBarBaz extends FooBar
{
use
ExampleTrait;
public function
output()
{
parent::output();
echo
"baz";
}
}

(new
FooBarBaz())->output();
?>

輸出
foo
bar
baz
84td84 at gmail dot com
9 年前
關於「範例 #9 靜態變數」的註解。trait 也可以擁有靜態屬性

trait Counter {
static $trvar=1;

public static function stfunc() {
echo "Hello world!"
}
}

class C1 {
use Counter;
}

print "\nTRVAR: " . C1::$trvar . "\n"; // 輸出 1

$obj = new C1();
C1::stfunc(); // 輸出 Hello world!
$obj->stfunc(); // 輸出 Hello world!

靜態屬性 (trvar) 只能使用類別名稱 (C1) 存取。
但是靜態函數 (stfunc) 可以使用類別名稱或實例 ($obj) 存取。
cody at codysnider dot com
7 年前
/*
當應用 trait 時,與類別或 trait 相關的 DocBlocks 不會被帶入。

嘗試在有和沒有 DocBlocks 的類別上進行一些變化,這些類別使用了帶有 DocBlock 的 trait 的結果
*/

<?php

/**
* @Entity
*/
trait Foo
{
protected
$foo;
}

/**
* @HasLifecycleCallbacks
*/
class Bar
{
use
\Foo;

protected
$bar;
}

class
MoreBar
{
use
\Foo;

protected
$moreBar;
}

$w = new \ReflectionClass('\Bar');
echo
$w->getName() . ":\r\n";
echo
$w->getDocComment() . "\r\n\r\n";

$x = new \ReflectionClass('\MoreBar');
echo
$x->getName() . ":\r\n";
echo
$x->getDocComment() . "\r\n\r\n";

$barObj = new \Bar();
$y = new \ReflectionClass($barObj);
echo
$y->getName() . ":\r\n";
echo
$y->getDocComment() . "\r\n\r\n";

foreach(
$y->getTraits() as $traitObj) {
echo
$y->getName() . " ";
echo
$traitObj->getName() . ":\r\n";
echo
$traitObj->getDocComment() . "\r\n";
}

$moreBarObj = new \MoreBar();
$z = new \ReflectionClass($moreBarObj);
echo
$z->getName() . " ";
echo
$z->getDocComment() . "\r\n\r\n";

foreach(
$z->getTraits() as $traitObj) {
echo
$z->getName() . " ";
echo
$traitObj->getName() . ":\r\n";
echo
$traitObj->getDocComment() . "\r\n";
}
artur at webprojektant dot pl
12 年前
Trait 不能與類別同名,因為會出現:致命錯誤:無法重新宣告類別
To Top