自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 traits。
Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用方法集。Traits 和类组合的语义是定义了一种方式来减少复杂性,避免传统多继承和混入类(Mixin)相关的典型问题。
Trait 和一个类相似,但仅仅旨在用细粒度和一致的方式来组合功能。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用类的成员不需要继承。
Example #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 则覆盖了被继承的方法。
Example #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!
Example #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 声明列出多个 trait,可以都插入到一个类中。
Example #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 操作符可以将其中一个冲突的方法以另一个名称来引入。
Example #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 语法还可以用来调整方法的访问控制。
Example #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 中的部分或全部成员。
Example #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 支持抽象方法的使用。
Example #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 的方法引用,但不能被 trait 定义。但是 trait 能够为使用的类定义静态方法。
Example #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
?>
Example #10 静态方法
<?php
trait StaticExample {
public static function doSomething() {
return 'Doing something';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
?>
Trait 同样可以定义属性。
Example #11 定义属性
<?php
trait PropertiesTrait {
public $x = 1;
}
class PropertiesExample {
use PropertiesTrait;
}
$example = new PropertiesExample;
$example->x;
?>
如果 trait 定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。如果该属性在类中的定义与在
trait 中的定义兼容(同样的可见性和初始值)则错误的级别是
E_STRICT
,否则是一个致命错误。
Example #12 解决冲突
<?php
trait PropertiesTrait {
public $same = true;
public $different = false;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true; // Strict Standards
public $different = true; // 致命错误
}
?>
Oddant (2013-05-29 15:10:12)
I think it's obvious to notice that using 'use' followed by the traits name must be seen as just copying/pasting lines of code into the place where they are used.
syzer3 at gmail dot com (2013-04-27 09:14:15)
Traits can be useful to create Fluent API
ex:
trait StaticMake
{
public static function make()
{
return new static();
}
}
class HelloWorld
{
use StaticMake;
public function getHello()
{
return "Hello World";
}
}
//now instead:
$theHello = new HelloWorld;
echo $theHello -> getHello();
//one may just use
echo HelloWorld:make() -> getHello();
jan (2013-03-07 18:51:17)
Hi! Just a quick solution to use class-identical static property. I use it to store SQL descriptions, which is always same for the same class, but may change for an extended class.
<?php
interface hasO{ // just for forcing basic functionalities
static function O($use_as);
function myObj($append = "nothing");
function inc();
static function getName();
}
class O{ // the target class to access
var $val, $num = 0;
function setVAl($val){$this->val = $val;}
function inc(){$this->num++;}
function prt($append){echo "hello, i'm {$this->val} with number: {$this->num} and append a string: {$append}<br>"; }
}
trait T{ // the trait to access O
private static $objs = [];
private static $preference = [];
static function setPreference($class){
self::$preference[get_called_class()] = $class; // we can hack even polymorphism
}
static function O($use_as = null){
/* we use $use_as, preference or the current calling class in this order. */
$cc = ($use_as ? $use_as : (($cc2 = @self::$preference[get_called_class()])!= null ? $cc2 : get_called_class()));
if (!@self::$objs[$cc]){ // you have to use 'self::' to access a private method.
self::$objs[$cc] = new O();
self::$objs[$cc]->setVal(static::getName()); // but you can use 'static::' to access class method.
}
return self::$objs[$cc];
}
}
class A implements hasO{ // A is independent from the other classes
use T;
static function getName() {return 'A';}
function myObj($append = "i'm A",$use_as = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
self::O($use_as)->prt($append);
}
function inc(){self::O()->inc();}
}
class B implements hasO{
use T;
function myObj($append = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
static::O()->prt($append);
}
function inc(){static::O()->inc();}
static function getName() {return get_called_class();}
}
class C extends B{} // not relevant inheritence.
class D extends B{
use T; // you won't get errors if you accidently reuse the same trait
static function getName() {return 'a child of B ';} // we can use current object static methods, so inheritence is intact
function inc(){
static::O()->inc(); // both self::O and static::O is usable to preserve further inherits
self::O()->inc();
}
}
class E extends B{
const CLSS = 'E';
function myObj($append="EEEE!", $use_as = null){
if (!$append && $append !== false) $append = "i'm ".get_called_class();
self::O($use_as)->prt($append);
}
}
$a = new A;$b = new B;$c = new C;$d = new D;$e = new E; // create all objects...
$b->myObj(); $b->inc(); $b->myObj();
$c->myObj(); $c->inc(); $c->myObj(); // C comes before A, so C's object is already instantized before A tries to access it
$a->myObj(); $a->inc(); $a->myObj();
$a->myObj(null,'C'); // this isn't works, because it's protected private
$d->myObj(); $d->inc(); $d->myObj();
$e->myObj(); $e->inc(); $e->myObj("i'm E again");
$e->myObj(0,'C'); // but this IS working, even if it's private NOT protected.
$e::setPreference('B'); // we want to morph to B
$e->myObj();
$e->inc(); // we incement B's object now as seen bellow
$e->myObj(); $b->myObj();
?>
Serafim (2013-01-26 20:38:48)
Another useful property of traits:
<?php
namespace Traits;
trait Properties{
public function __get($var){
$var = '_' . $var;
$getter = '_get' . $var;
if(method_exists($this, $getter)){
try{
$val = $this->$getter();
}catch(\Exception $e){
throw new \Exception($e);
}
return $val;
}
throw new \Exception('Can not get property: ' . $var . ', method ' . $getter . ' not exists');
}
public function __set($var, $val){
$var = '_' . $var;
$setter = '_set' . $var;
if(method_exists($this->$setter) && isset($this->$var)){
try{
$setval = $this->$setter($val);
}catch(\Exception $e){
throw new \Exception($e);
}
$this->$var = ($setval === NULL) ? $this->$var : $setval;
}else{
throw new \Exception('Can not set property: ' . $var . ', method ' . $setter . ' not exists');
}
}
}
class Some{
use \Chidori\Traits\Properties;
// Magic begin
protected $_var = 42;
protected function _get_var(){ return $this->_var; }
protected function _set_var($val){ return NULL; }
}
$s = new Some();
$s->var = 23; \\ set value
echo $s->var; \\ return 42? where is my 23? =)
?>
D. Marti (2012-11-01 15:25:03)
Traits are useful for strategies, when you want the same data to be handled (filtered, sorted, etc) differently.
For example, you have a list of products that you want to filter out based on some criteria (brands, specs, whatever), or sorted by different means (price, label, whatever). You can create a sorting trait that contains different functions for different sorting types (numeric, string, date, etc). You can then use this trait not only in your product class (as given in the example), but also in other classes that need similar strategies (to apply a numeric sort to some data, etc).
<?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>';
?>
artur at webprojektant dot pl (2012-09-28 18:10:56)
Trait can not have the same name as class because it will show: Fatal error: Cannot redeclare class
dario dot masamune at gmail dot com (2012-08-18 00:27:08)
Just in case someone was wondering, traits can be inside namespaces too! Cool!!
t8 at AT pobox dot com (2012-07-23 20:17:13)
Another difference with traits vs inheritance is that methods defined in traits can access methods and properties of the class they're used in, including private ones.
For example:
<?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'
?>
karolis at iwsolutions dot ie (2012-07-18 09:16:38)
Not very obvious but trait methods can be called as if they were defined as static methods in a regular class
<?php
trait Foo {
function bar() {
return 'baz';
}
}
echo Foo::bar(),"\\n";
?>
ryanhanekamp at yahoo dot com (2012-05-10 00:14:34)
Using AS on a __construct method (and maybe other magic methods) is really, really bad. The problem is that is doesn't throw any errors, at least in 5.4.0. It just sporadically resets the connection. And when I say "sporadically," I mean that arbitrary changes in the preceding code can cause the browser connection to reset or not reset *consistently*, so that subsequent page refreshes will continue to hang, crash, or display perfectly in the same fashion as the first load of the page after a change in the preceding code, but the slightest change in the code can change this state. (I believe it is related to precise memory usage.)
I've spent a good part of the day chasing down this one, and weeping every time commenting or even moving a completely arbitrary section of code would cause the connection to reset. It was just by luck that I decided to comment the
"__construct as primitiveObjectConstruct"
line and then the crashes went away entirely.
My parent trait constructor was very simple, so my fix this time was to copy the functionality into the child __construct. I'm not sure how I'll approach a more complicated parent trait constructor.
ryan at derokorian dot com (2012-04-15 02:03:15)
Simple singleton 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;
Anonymous (2012-03-27 21:30:52)
Traits can not implement interfaces.
(should be obvious, but tested is tested)
Safak Ozpinar / safakozpinar at gmail (2012-03-18 03:03:12)
Unlike inheritance; if a trait has static properties, each class using that trait has independent instances of those properties.
Example using parent class:
<?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; // Prints: World World
?>
Example using 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; // Prints: Hello World
?>
Jason dot Hofer dot deletify dot this dot part at gmail dot com (2012-03-14 16:39:59)
A (somewhat) practical example of trait usage.
Without traits:
<?php
class Controller {
/* Controller-specific methods defined here. */
}
class AdminController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods defined here. */
}
class CrudController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* CRUD-specific methods defined here. */
}
class AdminCrudController extends CrudController {
/* Controller-specific methods inherited from Controller. */
/* CRUD-specific methods inherited from CrudController. */
/* (!!!) Admin-specific methods copied and pasted from AdminController. */
}
?>
With traits:
<?php
class Controller {
/* Controller-specific methods defined here. */
}
class AdminController extends Controller {
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods defined here. */
}
trait CrudControllerTrait {
/* CRUD-specific methods defined here. */
}
class AdminCrudController extends AdminController {
use CrudControllerTrait;
/* Controller-specific methods inherited from Controller. */
/* Admin-specific methods inherited from AdminController. */
/* CRUD-specific methods defined by CrudControllerTrait. */
}
?>
farhad dot peb at gmail dot com (2012-03-09 19:29:11)
<?php
trait first_trait
{
function first_function()
{
echo "From First Trait";
}
}
trait second_trait
{
function first_function()
{
echo "From Second Trait";
}
}
class first_class
{
use first_trait, second_trait
{
// This class will now call the method
// first function from first_trait only
first_trait::first_function insteadof second_trait;
// first_function of second_traits can be
// accessed with second_function
second_trait::first_function as second_function;
}
}
$obj = new first_class();
// Output: From First Trait
$obj->first_function();
// Output: From Second Trait
$obj->second_function();
?>
the iranian php programmer
writer: farhad zand
farhad.peb@gmail.com
php_engineer_bk@yahoo.com
anthony bishopric (2012-03-03 00:11:50)
The magic method __call works as expected using traits.
<?php
trait Call_Helper{
public function __call($name, $args){
return count($args);
}
}
class Foo{
use Call_Helper;
}
$foo = new Foo();
echo $foo->go(1,2,3,4); // echoes 4
Edward (2012-03-01 03:29:04)
The difference between Traits and multiple inheritance is in the inheritance part. A trait is not inherited from, but rather included or mixed-in, thus becoming part of "this class". Traits also provide a more controlled means of resolving conflicts that inevitably arise when using multiple inheritance in the few languages that support them (C++). Most modern languages are going the approach of a "traits" or "mixin" style system as opposed to multiple-inheritance, largely due to the ability to control ambiguities if a method is declared in multiple "mixed-in" classes.
Also, one can not "inherit" static member functions in multiple-inheritance.
greywire at gmail dot com (2012-02-16 19:12:22)
The best way to understand what traits are and how to use them is to look at them for what they essentially are: language assisted copy and paste.
If you can copy and paste the code from one class to another (and we've all done this, even though we try not to because its code duplication) then you have a candidate for a trait.
chris dot rutledge at gmail dot com (2011-12-21 00:42:38)
It may be worth noting here that the magic constant __CLASS__ becomes even more magical - __CLASS__ will return the name of the class in which the trait is being used.
for example
<?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
?>
The magic constant __TRAIT__ will giev you the name of the trait