PHP Conference Japan 2024

Trait

PHP 实现了一种称为 Trait 的代码复用方式。

Trait 是在单继承语言(如 PHP)中复用代码的一种机制。Trait 旨在通过使开发人员能够在不同类层次结构中的多个独立类中自由地复用方法集,来减少单继承的一些限制。 Trait 和类的组合语义的定义方式降低了复杂性,并避免了与多重继承和 Mixin 相关的典型问题。

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!

多个 Trait

可以通过在 use 语句中列出多个 Trait,并用逗号分隔,将它们插入到一个类中。

示例 #4 多个 Trait 的使用

<?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!

冲突解决

如果两个 Trait 插入同名的方法,并且冲突没有被显式解决,将会产生致命错误。

为了解决在同一个类中使用的 Trait 之间的命名冲突,需要使用 insteadof 运算符来选择其中一个冲突的方法。

由于这仅允许排除方法,因此可以使用 as 运算符为其中一个方法添加别名。请注意,as 运算符不会重命名该方法,也不会影响任何其他方法。

示例 #5 冲突解决

在这个例子中,Talker 使用了 trait 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; }
}
?>

由 Trait 组成的 Trait

正如类可以使用 trait 一样,其他 trait 也可以使用 trait。通过在 trait 定义中使用一个或多个 trait,它可以部分或完全由其他 trait 中定义的成员组成。

示例 #7 由 Trait 组成的 Trait

<?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 成员

Trait 支持使用抽象方法来对实现它的类施加要求。支持公共、受保护和私有方法。在 PHP 8.0.0 之前,仅支持公共和受保护的抽象方法。

注意

从 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;
}
}
?>

特性的静态成员

特性可以定义静态变量、静态方法和静态属性。

注意:

自 PHP 8.1.0 起,直接在特性上调用静态方法或访问静态属性已被弃用。静态方法和属性应该只能通过使用该特性的类来访问。

示例 #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(); // 输出 1
$p = new C2(); $p->inc(); // 输出 1
?>

示例 #10 静态方法

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

class
Example {
use
StaticExample;
}

Example::doSomething();
?>

示例 #11 静态属性

注意

在 PHP 8.3.0 之前,静态属性在所有使用该特性的类之间共享。从 PHP 8.3.0 开始,每个使用该特性的类都有其自己的静态属性副本。

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

class
Example {
use
StaticExample;
}

echo
Example::$static;
?>

属性

特性也可以定义属性。

示例 #12 定义属性

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

class
PropertiesExample {
use
PropertiesTrait;
}

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

如果一个特性定义了一个属性,那么一个类不能定义具有相同名称的属性,除非它是兼容的(相同的可见性和类型,只读修饰符和初始值),否则会发出致命错误。

示例 #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 开始,特性也可以定义常量。

示例 #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 methods)

从 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 年前
理解 trait 是什么以及如何使用它们的最佳方法是将它们视为其本质:语言辅助复制和粘贴。

如果您可以将代码从一个类复制并粘贴到另一个类(并且我们都这样做过,即使我们尽量避免这样做,因为它会导致代码重复),那么您就找到了一个 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 年前
我没有见过这种具体的用例

“想要保留父类方法的动作,特质方法调用 ::parent 以及子类方法的动作”。

// 子类.
use SuperTrait {
initialize as initializeOr;
}
public function initialize(array &$element) {
...
$this->initializeOr($element);
}
// 特质.
public function initialize(array &$element) {
...
parent::initialize($element);
}
// 父类.
public function initialize(array &$element) {
...
}
t8 at AT pobox dot com
12 年前
Trait 与继承的另一个区别是,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”关键字无效。

<?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 {
// 致命错误:无法覆盖..中 Foo::hello() 的最终方法
final public function hello($s) { print "hello, $s!"; }
}
?>

但是这种方法将按预期完成 Trait 方法;

<?php
trait FooTrait {
final public function
hello($s) { print "$s, hello!"; }
}
abstract class
Foo {
use
FooTrait;
}
class
Bar extends Foo {
// 致命错误:无法覆盖..中 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 共享,并且确实如此。

以这种方式查看可以解释上面观察到的很多关于 'quirks' 的疑问,这些疑问与 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 年前
(已经说过了,但为了搜索“相对”这个词……)

在类中导入 trait 的“use”关键字将解析为相对于当前命名空间,因此应包含前导斜杠来表示完整路径,而命名空间级别的“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(); // 输出:Amber
Ale::printed(); // 输出:Light
Lager::printed(); // 输出: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 年前
Trait 和多重继承的区别在于继承部分。Trait 不是继承的,而是被包含或混入的,从而成为“这个类”的一部分。Trait 还提供了一种更可控的方式来解决在少数支持多重继承的语言(C++)中使用多重继承时不可避免地出现的冲突。大多数现代语言都采用“trait”或“mixin”风格的系统,而不是多重继承,这主要是因为如果一个方法在多个“混入”的类中声明,则能够控制歧义。

此外,在多重继承中不能“继承”静态成员函数。
ryan at derokorian dot com
12 年前
简单的单例 trait。

<?php

trait singleton {
/**
* 私有构造函数,通常由使用类定义
*/
//private function __construct() {}

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

public function
__clone() {
trigger_error('克隆 '.__CLASS__.' 是不允许的。',E_USER_ERROR);
}

public function
__wakeup() {
trigger_error('反序列化 '.__CLASS__.' 是不允许的。',E_USER_ERROR);
}
}

/**
* 使用示例
*/

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.net/manual/en/language.oop5.late-static-bindings.php),那么魔术常量 __CLASS__ 在 trait 中使用的行为与预期一致。

<?php

$format
= '类: %-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();

?>

将输出

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

由于 Trait 被认为是代码的字面“复制/粘贴”,因此 DuplicateTrait 中定义的方法如何给出与 AbstractClass 中定义的方法相同的结果就很清楚了。
D. Marti
12 年前
Trait 对于策略很有用,当您希望以不同的方式处理(过滤、排序等)相同的数据时。

例如,您有一个产品列表,您希望根据某些条件(品牌、规格等)过滤掉这些产品,或者按不同的方式(价格、标签等)排序。您可以创建一个排序 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' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
101219 => array('label' => 'Meh product', '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

已弃用:与类同名的方法在未来的 PHP 版本中将不再是构造函数;E 在 /in/mFuQE 的第 48 行有一个已弃用的构造函数
AaBa

致命错误:Trait 方法 a 尚未应用,因为与 F 上的其他 Trait 方法冲突,位于 /in/mFuQE 的第 65 行
Kristof
10 年前
别忘了,您也可以创建复杂的(嵌入式)Trait

<?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”后跟 Trait 名称必须被视为只是将代码行复制/粘贴到使用它们的位置。
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)不会被继承。

尝试在使用带有文档块的 trait 的类上使用和不使用文档块的结果
*/

<?php

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

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

protected
$bar;
}

class
MoreBar
{
use
\Foo;

protected
$moreBar;
}

// ... 省略代码,与英文原文相同 ...
artur at webprojektant dot pl
12 年前
Trait 不能与类同名,否则会显示:致命错误:无法重新声明类
To Top