特质

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

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

特质类似于类,但仅旨在以细粒度和一致的方式对功能进行分组。无法单独实例化特质。它是对传统继承的补充,并支持行为的横向组合;也就是说,应用类成员而不必继承。

示例 #1 特质示例

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

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

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

优先级

从基类继承的成员将被特质插入的成员覆盖。优先级顺序是:当前类的成员覆盖特质方法,特质方法依次覆盖继承的方法。

示例 #2 优先级顺序示例

从基类继承的方法被从 SayWorld 特质中插入 MyHelloWorld 的方法覆盖。对于在 MyHelloWorld 类中定义的方法,行为相同。优先级顺序是:当前类的成员覆盖特质方法,特质方法依次覆盖基类的方法。

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

多个特质

可以通过在 use 语句中列出它们(用逗号分隔)将多个特质插入类中。

示例 #4 多个特质用法

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

冲突解决

如果两个特质插入了具有相同名称的方法,则会产生致命错误,除非显式地解决了冲突。

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

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

示例 #5 冲突解决

在此示例中,Talker 使用了 A 和 B 特质。由于 A 和 B 具有冲突的方法,因此它定义了使用特质 B 中的 smallTalk 变体和特质 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 也可以相互利用。通过在一个 Traits 定义中使用一个或多个 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 支持使用抽象方法,以便对显示类施加要求。支持公有、受保护和私有方法。在 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;
}
}
?>

静态 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
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 定义了一个属性,那么类不能定义具有相同名称的属性,除非它兼容(相同的可见性和类型、只读修饰符和初始值),否则将发出致命错误。

示例 #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 定义了一个常量,那么一个类不能定义相同名称的常量,除非它是兼容的(相同可见性、初始值和 finality),否则会发出致命错误。

示例 #15 冲突解决

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

class
ConstantsExample {
use
ConstantsTrait;
public const
FLAG_IMMUTABLE = 5; // 致命错误
}
?>
添加笔记

用户贡献笔记 25 notes

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
10 年前
请注意,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 年前
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 年前
请记住,"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!"; }
}
?>
canufrank
7 年前
一些笔记关于 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 变量相关
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 {
// 将参与冲突解决的函数的可见性设置为 public
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
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" 始终是绝对的。
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('Cloning '.__CLASS__.' is not allowed.',E_USER_ERROR);
}

public function
__wakeup() {
trigger_error('Unserializing '.__CLASS__.' is not allowed.',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;
Edward
12 年前
Traits 与多重继承之间的区别在于继承部分。Traits 不会从其他类继承,而是被包含或混合进来,从而成为“该类”的一部分。Traits 还提供了一种更受控的方式来解决在少数支持多重继承的语言(如 C++)中不可避免地出现的冲突。由于能够控制在多个“混合”类中声明方法时的歧义,大多数现代语言都采用了“Traits”或“Mixin”风格系统,而不是多重继承。

此外,在多重继承中不能“继承”静态成员函数。
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 方法被排除。
marko at newvibrations dot net
7 年前
如前所述,特质中的静态属性和方法可以直接使用特质访问。由于特质是语言辅助的复制粘贴,您应该知道,来自特质的静态属性将在类声明时被初始化为特质属性当时具有的值。

示例

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

?>
katrinaelaine6 at gmail dot com
6年前
补充 "atorich at gmail dot com" 的内容

如果理解特质和延迟静态绑定 (https://php.net/manual/en/language.oop5.late-static-bindings.php),那么在特质中使用魔术常量 __CLASS__ 的行为是符合预期的。

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

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

例如,您有一个产品列表,您希望根据一些条件 (品牌、规格等) 对其进行过滤,或者通过不同的方式 (价格、标签等) 对其进行排序。您可以创建一个排序特质,其中包含不同排序类型的不同函数 (数字、字符串、日期等)。然后,您不仅可以在产品类中使用此特质 (如示例中所示),还可以在其他需要类似策略的类中使用它 (对一些数据应用数字排序等)。

<?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>';
?>
Kristof
10 年前
不要忘记,您也可以创建复杂的(嵌入式)特征

<?php
trait Name {
// ...
}
trait
Address {
// ...
}
trait
Telephone {
// ...
}
trait
Contact {
use
Name, Address, Telephone;
}
class
Customer {
use
Contact;
}
class
Invoce {
use
Contact;
}
?>
bscheshirwork at gmail dot com
6年前
https://3v4l.org/mFuQE

1. 如果从特征中获取同名类方法,则不会出现弃用
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: Methods with the same name as their class will not be constructors in a future version of PHP; E has a deprecated constructor in /in/mFuQE on line 48
AaBa

致命错误:特征方法 a 未应用,因为在 /in/mFuQE 第 65 行存在与 F 上其他特征方法的冲突。
Carlos Alberto Bertholdo Carucce
8年前
如果你想解决命名冲突并更改特征方法的可见性,你需要在同一行声明两者。

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())
guidobelluomo at gmail dot com
3 年前
如果你覆盖了一个由特征定义的方法,调用父方法也会调用特征的覆盖。因此,如果你需要从包含特征的类派生,你可以扩展类而不会丢失特征的功能。

<?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
cody at codysnider dot com
7 年前
/*
与类或特征相关的 DocBlock 在应用特征时不会被继承。

结果尝试了几个带有和不带有 DocBlock 的类的变体,这些类使用带有 DocBlock 的特征。
*/

<?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";
}
84td84 at gmail dot com
9 年前
关于“示例 #9 静态变量”的一点说明。特征也可以拥有静态属性。

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)访问。
artur at webprojektant dot pl
11 年前
特征不能与类同名,因为会显示:致命错误:无法重新声明类。
Oddant
11 年前
我认为很明显的是,使用 'use' 然后是特征名称应该被视为仅仅将代码行复制/粘贴到使用它们的地方。
To Top