详解PHP中的Traits


Posted in PHP onJuly 29, 2015

PHP是单继承的语言,在PHP 5.4 Traits出现之前,PHP的类无法同时从两个基类继承属性或方法。php的Traits和Go语言的组合功能类似,通过在类中使用use关键字声明要组合的Trait名称,而具体某个Trait的声明使用trait关键词,Trait不能直接实例化。具体用法请看下面的代码:

<?php
  trait Drive {
    public $carName = 'trait';
    public function driving() {
      echo "driving {$this->carName}\n";
    }
  }
  class Person {
    public function eat() {
      echo "eat\n";
    }
  }
  class Student extends Person {
    use Drive;
    public function study() {
      echo "study\n";
    }
  }
  $student = new Student();
  $student->study();
  $student->eat();
  $student->driving();

输出结果如下:

study
eat
driving trait

上面的例子中,Student类通过继承Person,有了eat方法,通过组合Drive,有了driving方法和属性carName。

如果Trait、基类和本类中都存在某个同名的属性或者方法,最终会保留哪一个呢?通过下面的代码测试一下:

<?php 
  trait Drive {
    public function hello() {
      echo "hello drive\n";
    }
    public function driving() {
      echo "driving from drive\n";
    }
  }
  class Person {
    public function hello() {
      echo "hello person\n";
    }
    public function driving() {
      echo "driving from person\n";
    }
  }
  class Student extends Person {
    use Drive;
    public function hello() {
      echo "hello student\n";
    }
  }
  $student = new Student();
  $student->hello();
  $student->driving();

输出结果如下:

hello student
driving from drive

因此得出结论:当方法或属性同名时,当前类中的方法会覆盖 trait的 方法,而 trait 的方法又覆盖了基类中的方法。

如果要组合多个Trait,通过逗号分隔 Trait名称:

use Trait1, Trait2;

如果多个Trait中包含同名方法或者属性时,会怎样呢?答案是当组合的多个Trait包含同名属性或者方法时,需要明确声明解决冲突,否则会产生一个致命错误。

<?php
trait Trait1 {
  public function hello() {
    echo "Trait1::hello\n";
  }
  public function hi() {
    echo "Trait1::hi\n";
  }
}
trait Trait2 {
  public function hello() {
    echo "Trait2::hello\n";
  }
  public function hi() {
    echo "Trait2::hi\n";
  }
}
class Class1 {
  use Trait1, Trait2;
}

输出结果如下:

PHP Fatal error: Trait method hello has not been applied, because there are collisions with other trait methods on Class1 in ~/php54/trait_3.php on line 20

使用insteadof和as操作符来解决冲突,insteadof是使用某个方法替代另一个,而as是给方法取一个别名,具体用法请看代码:

<?php
trait Trait1 {
  public function hello() {
    echo "Trait1::hello\n";
  }
  public function hi() {
    echo "Trait1::hi\n";
  }
}
trait Trait2 {
  public function hello() {
    echo "Trait2::hello\n";
  }
  public function hi() {
    echo "Trait2::hi\n";
  }
}
class Class1 {
  use Trait1, Trait2 {
    Trait2::hello insteadof Trait1;
    Trait1::hi insteadof Trait2;
  }
}
class Class2 {
  use Trait1, Trait2 {
    Trait2::hello insteadof Trait1;
    Trait1::hi insteadof Trait2;
    Trait2::hi as hei;
    Trait1::hello as hehe;
  }
}
$Obj1 = new Class1();
$Obj1->hello();
$Obj1->hi();
echo "\n";
$Obj2 = new Class2();
$Obj2->hello();
$Obj2->hi();
$Obj2->hei();
$Obj2->hehe();

输出结果如下:

Trait2::hello
Trait1::hi

Trait2::hello
Trait1::hi
Trait2::hi
Trait1::hello

as关键词还有另外一个用途,那就是修改方法的访问控制:

<?php
  trait Hello {
    public function hello() {
      echo "hello,trait\n";
    }
  }
  class Class1 {
    use Hello {
      hello as protected;
    }
  }
  class Class2 {
    use Hello {
      Hello::hello as private hi;
    }
  }
  $Obj1 = new Class1();
  $Obj1->hello(); # 报致命错误,因为hello方法被修改成受保护的
  $Obj2 = new Class2();
  $Obj2->hello(); # 原来的hello方法仍然是公共的
  $Obj2->hi(); # 报致命错误,因为别名hi方法被修改成私有的

Trait 也能组合Trait,Trait中支持抽象方法、静态属性及静态方法,测试代码如下:

<?php
trait Hello {
  public function sayHello() {
    echo "Hello\n";
  }
}
trait World {
  use Hello;
  public function sayWorld() {
    echo "World\n";
  }
  abstract public function getWorld();
  public function inc() {
    static $c = 0;
    $c = $c + 1;
    echo "$c\n";
  }
  public static function doSomething() {
    echo "Doing something\n";
  }
}
class HelloWorld {
  use World;
  public function getWorld() {
    return 'get World';
  }
}
$Obj = new HelloWorld();
$Obj->sayHello();
$Obj->sayWorld();
echo $Obj->getWorld() . "\n";
HelloWorld::doSomething();
$Obj->inc();
$Obj->inc();

输出结果如下:

Hello
World
get World
Doing something
1
2

以上就是本文的全部内容,希望对大家的学习有所帮助。

PHP 相关文章推荐
增加反向链接的101个方法 站长推荐
Jan 31 PHP
PHP file_get_contents 函数超时的几种解决方法
Jul 30 PHP
PHP explode()函数用法、切分字符串
Oct 03 PHP
利用php+mcDropdown实现文件路径可在下拉框选择
Aug 07 PHP
PHP中如何实现常用邮箱的基本判断
Jan 07 PHP
Zend Framework教程之Zend_Layout布局助手详解
Mar 04 PHP
PHP数学运算函数大汇总(经典值得收藏)
Apr 01 PHP
Yii2框架使用计划任务的方法
May 25 PHP
php表单处理操作
Nov 16 PHP
总结PHP代码规范、流程规范、git规范
Jun 18 PHP
微信JSSDK分享功能图文实例详解
Apr 08 PHP
Yii框架操作cookie与session的方法实例详解
Sep 04 PHP
php实现在多维数组中查找特定value的方法
Jul 29 #PHP
1亿条数据如何分表100张到Mysql数据库中(PHP)
Jul 29 #PHP
php实现仿写CodeIgniter的购物车类
Jul 29 #PHP
PHP使用逆波兰式计算工资的方法
Jul 29 #PHP
ThinkPHP实现递归无级分类――代码少
Jul 29 #PHP
PHP之正则表达式捕获组与非捕获组(详解)
Jul 29 #PHP
PHP实现的简单缓存类
Jul 29 #PHP
You might like
无限级别菜单的实现
2006/10/09 PHP
用php和MySql来与ODBC数据连接
2006/10/09 PHP
PHP6 先修班 JSON实例代码
2008/08/23 PHP
php字符串分割函数explode的实例代码
2013/02/07 PHP
探讨PHP使用eAccelerator的API开发详解
2013/06/09 PHP
php获取参数的几种方法总结
2014/02/18 PHP
PHP中使用Memache作为进程锁的操作类分享
2015/03/30 PHP
PHP下SSL加密解密、验证、签名方法(很简单)
2020/06/28 PHP
yii2局部关闭(开启)csrf的验证的实例代码
2017/07/10 PHP
Laravel 5.5基于内置的Auth模块实现前后台登陆详解
2017/12/21 PHP
使用JavaScript switch case 另类写法
2010/03/14 Javascript
判断客户端浏览器是否安装了Flash插件的多种方法
2010/08/11 Javascript
jquery中文乱码的多种解决方法
2013/06/21 Javascript
jquery bind(click)传参让列表中每行绑定一个事件
2014/08/06 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery添加和删除输入文本框标签代码
2016/05/20 Javascript
老生常谈JavaScript 函数表达式
2016/09/01 Javascript
AngularJS常见过滤器用法实例总结
2017/07/06 Javascript
浅谈Vue路由快照实现思路及其问题
2018/06/07 Javascript
微信小程序开发实现的选项卡(窗口顶部/底部TabBar)页面切换功能图文详解
2019/05/14 Javascript
js观察者模式的弹幕案例
2020/11/23 Javascript
Python3通过Luhn算法快速验证信用卡卡号的方法
2015/05/14 Python
Pandas之drop_duplicates:去除重复项方法
2018/04/18 Python
python查看文件大小和文件夹内容的方法
2019/07/08 Python
Python实现Wordcloud生成词云图的示例
2020/03/30 Python
Jupyter notebook运行Spark+Scala教程
2020/04/10 Python
python3中布局背景颜色代码分析
2020/12/01 Python
美国和加拿大房车出售在线分类广告:RVT.com
2018/04/23 全球购物
JBL澳大利亚官方商店:扬声器、耳机和音响系统
2018/05/24 全球购物
经典优秀个人求职自荐信格式
2013/09/25 职场文书
七夕活动策划方案
2014/08/16 职场文书
晚自修旷课检讨书怎么写
2014/11/17 职场文书
党建工作汇报材料
2014/12/24 职场文书
个人年终总结结尾
2015/03/06 职场文书
幼儿园教师安全责任书
2015/05/08 职场文书
银行客户经理培训心得体会
2016/01/09 职场文书