php trait特性详解及使用注意事项

php trait特性

简介

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

Trait简单的来讲就是将trait里的代码copy一份到类中。(个人理解)

基础使用

trait TestTrait {
    public function helloWorld() {
        echo 'hello world';
    }
}

class Demo{
    use TestTrait;
}

(new Demo())->helloWorld(); // hello world

优先级

看到这可能会想如何结合extend它的优先级是怎样的?同一个方法谁先执行?我们看代码是怎么运行的。

trait TestTrait {
    public function helloWorld() {
        echo 'TestTrait2 - hello world';
    }
}


class Base {
    public function helloWorld() {
        echo 'base - hello world';
    }
}

class Demo extends Base {
    // 没有引入trait
}

class Demo2 extends Base {
    use TestTrait;
}

class Demo3 extends Base {
    use TestTrait;

    public function helloWorld() {
        echo 'Demo3 - hello world';
    }
}

(new Demo())->helloWorld(); // base - hello world
(new Demo2())->helloWorld(); // TestTrait2 - hello world
(new Demo3())->helloWorld(); // Demo3 - hello world

结论:内部方法>trait>extend

多个trait

基本使用

通过使用“,”可以在use的适合加载多个trait

trait TestTrait_1 {
    public function hello() {
        echo 'hello';
    }
}

trait TestTrait_2 {
    public function world() {
        echo ' world';
    }
}

class Demo {
    use TestTrait_1,TestTrait_2;// 和下面加载方式等价
//  use TestTrait_1;
//  use TestTrait_2;
}
$demo = new Demo();
$demo->hello();
$demo->world(); // echo 'hello world';

解决冲突,insteadof和as关键字

在加载多个的trait时候插入了相同的方法名会导致方法冲突,例如下面这个情况

trait TestTrait_1 {
    public function helloWorld() {
        echo 'Test_1 hello world';
    }
}

trait TestTrait_2 {
    public function helloWorld() {
        echo 'Test_2 hello world';
    }
}

class Demo {
    use TestTrait_1, TestTrait_2;
}

(new Demo())->helloWorld();
// 报错内容:Fatal error: Trait method helloWorld has not been applied, because there are collisions with other trait methods on Demo

为了解决这个冲突就需要使用insteadof关键字来指定使用冲突方法中哪一个。我们使用上面的情况来演示

trait TestTrait_1 {
    public function helloWorld() {
        echo 'Test_1 hello world';
    }
}

trait TestTrait_2 {
    public function helloWorld() {
        echo 'Test_2 hello world';
    }
}

class Demo {
    use TestTrait_1, TestTrait_2 {
        // 使用TestTrait_2代替TestTrait_1里面的helloWorld。可以简单理解为:TestTrait_2里的helloWorld替换了TestTrait_1里的helloWorld‘
        TestTrait_2::helloWorld insteadof TestTrait_1;
    }
}

(new Demo())->helloWorld(); // echo 'Test_2 hello world';

insteadof 是用来解决冲突的,那么as是干嘛的呢?我们来看下例子:

class Demo {
    use TestTrait_1, TestTrait_2 {
        TestTrait_2::helloWorld insteadof TestTrait_1;
        // as 即为为这个方法起个别名(第二个名字)
        TestTrait_2::helloWorld as hello;
        // as 同时还可以修改方法的控制权
        TestTrait_2::helloWorld as private;
        // as 可以结合上面同时使用。
        //意思即为:为estTrait_2::helloWorld起个别名为protected_hello方法的属性为protected(这个属性在外面是无法访问的)
        TestTrait_2::helloWorld as protected protected_hello;
    }
}

(new Demo())->hello(); // echo 'Test_2 hello world';
(new Demo())->protected_hello(); // 报错,protected属性不能被访问

看例子大家应该能看懂了,但是不知道你们有没有发现一个问题,这里as被使用了三次,其中TestTrait_2::helloWorld as private;这行代码已经转移了控制权为什么还能访问hello呢?经过测试可以这么理解,看代码:

class Demo {
    use TestTrait_1, TestTrait_2 {
        TestTrait_2::helloWorld insteadof TestTrait_1;
        // 将helloWorld命名为了hello
        TestTrait_2::helloWorld as hello;
        // 将helloWorld控制权从public转为hello
        TestTrait_2::helloWorld as private;
        // 将helloWorld命名为了protected_hello并将protected_hello的控制权改为protected
        TestTrait_2::helloWorld as protected protected_hello;
    }

    // 可以这么理解TestTrait_2::helloWorld as hello;
    public function hello() {
        $this->helloWorld();
    }

    // 可以这么理解TestTrait_2::helloWorld as protected protected_hello;
    // 所以在调用(new Demo())->protected_hello()的时候调用失败
    protected function protected_hello() {
        $this->helloWorld();
    }

    // TestTrait_2::helloWorld as private;
    // 就是将helloWorld这个方法的控制权改为了private,类的内部还是可以调用,外部不可以调用。
    // 上面说过trait实际是上是将里面的代码copy一份到类中,那么实际上Demo这个是这样的
    // class Demo {
    //  use TestTrait_1, TestTrait_2 {
    //      TestTrait_2::helloWorld insteadof TestTrait_1;
    //  }
    // }
    // 等价于
    //class Demo{
    //  public function helloWorld() {
    //      echo 'Test_2 hello world';
    //  }
    //}
    // 然后public属性改为private 在类的内部还是可以使用,外部就不能使用了。
}

相信对as能够区分了,但是这里还有一个问题,上面说的是方法冲突,那么成员属性冲突怎么解决呢?

在 PHP 7.0 之前,在类里定义和 trait 同名的属性,哪怕是完全兼容的也会抛出 E_STRICT(完全兼容的意思:具有相同的访问可见性、初始默认值)。

也就是说使用7.0之前的版本要注意属性冲突才行。但是有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。

trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // PHP 7.0.0 后没问题,之前版本是 E_STRICT 提醒
    public $different = true; // 致命错误
}

总结

  • trait在php5.4后才支持
  • 是将trait里的代码copy一份到类中(理解这个可以避免很多错误)
  • trait优先级:内部方法>trait>extend
  • trait解决方法名冲突使用insteadof关键字,修改控制权和起别名使用as关键字
  • 使用trait要避免属性名称冲突,起名可以使用前缀,例如:public $demo_name=”;来避免冲突。
  • trait在php7.0之前相同的属性和值会报错,7.0之后不会。修改trait的属性值会报错。

如果有错误地方欢迎指正。

文章来源: php trait特性详解及使用注意事项

人吐槽 人点赞

猜你喜欢

发表评论

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

查看评论:php trait特性详解及使用注意事项