PHP5.4新特性:traits

从PHP 5.4开始实现了代码重用的的新方法traits。

traits是一种单继承语言(比如PHP)的代码重用机制,是为了解决单继承语言的局限性,在避免多重继承的复杂性的同时,又兼顾多重继承的代码重用的优点。

traits类似于class,traits不能被实例化,不能被继承,只能被横向的组合(使用use)。

trait实例:

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

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

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

优先权

如果一个从基类继承的方法和一个使用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!

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

使用逗号可以组合多个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中定义的方法有重名的情况,将会产生一个致命错误,如果没有显示定义引入的方法。

解决一个类中插入trait重名的方法,可以使用insteadof操作符来定义具体引用哪个trait中的方法。

使用insteadof只能引入一个方法,可以同时使用as操作符给同名的方法换一个方法名。

<?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语法,可以改变方法的可见性

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

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

trait中包含trait

通类一样,在trait中同样可以使用use语法来组合多个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成员

traits支持使用抽象(abstract)方法。

<?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的方法中可以使用静态(static)变量,方法也可以定义为静态方法,但是不能定义为trait的静态变量。

静态变量实例

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

静态方法实例

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

class Example {
    use StaticExample;
}

Example::doSomething();
?>

属性

traits同样支持属性

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

class PropertiesExample {
    use PropertiesTrait;
}

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

如果trait中定义一个属性,那么引用这个trait的类中就不能定义相同的属性名,如果类中定义的同名属性和trait中定义的属性值相同将会引发一个E_STRICT,值不一样的化将会引发一个致命错误。