PHP 实现代码复用的一个方法 traits新特性


Posted in PHP onFebruary 22, 2015

在阅读yii2源码的时候接触到了trait,就学习了一下,写下博客记录一下。

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

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

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

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 则覆盖了被继承的方法。

优先顺序示例

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

从基类继承的成员被插入的 SayWorld Trait 中的 sayHello 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。

另一个优先级顺序的例子

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

通过逗号分隔,在 use 声明列出多个 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 在同一个类中的命名冲突,需要使用 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;

    }

}

?>

在本例中 Talker 使用了 trait A 和 B。由于 A 和 B 有冲突的方法,其定义了使用 trait B 中的 smallTalk 以及 trait A 中的 bigTalk。

Aliased_Talker 使用了 as 操作符来定义了 talk 来作为 B 的 bigTalk 的别名。

修改方法的访问控制

使用 as 语法还可以用来调整方法的访问控制。

修改方法的访问控制的例子

<?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 定义时通过使用一个或多个 trait,它能够组合其它 trait 中的部分或全部成员。

从 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 的抽象成员

为了对使用的类施加强制要求,trait 支持抽象方法的使用。

表示通过抽象方法来进行强制要求的例子

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

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();

?>

静态变量和静态方法的例子

<?php

trait Counter {

    public static $c = 0;

    public static function inc() {

        self::$c = self::$c + 1;

        echo self::$c . "\n";

    }

}

class C1 {

    use Counter;

}

class C2 {

    use Counter;

}

C1::inc(); // echo 1

C2::inc(); // echo 1

?>

属性
Trait 同样可以定义属性。

定义属性的例子

<?php

trait PropertiesTrait {

    public $x = 1;

}

class PropertiesExample {

    use PropertiesTrait;

}

$example = new PropertiesExample;

$example->x;

?>

如果 trait 定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。如果该属性在类中的定义与在 trait 中的定义兼容(同样的可见性和初始值)则错误的级别是 E_STRICT,否则是一个致命错误。

冲突的例子

<?php

trait PropertiesTrait {

    public $same = true;

    public $different = false;

}

class PropertiesExample {

    use PropertiesTrait;

    public $same = true; // Strict Standards

    public $different = true; // 致命错误

}

?>

Use的不同

不同use的例子

<?php

namespace Foo\Bar;

use Foo\Test;  // means \Foo\Test - the initial \ is optional

?>

<?php

namespace Foo\Bar;

class SomeClass {

    use Foo\Test;   // means \Foo\Bar\Foo\Test

}

?>

第一个use是用于 namespace 的 use Foo\Test,找到的是 \Foo\Test,第二个 use 是使用一个trait,找到的是\Foo\Bar\Foo\Test。

__CLASS__和__TRAIT__
__CLASS__ 返回 use trait 的 class name,__TRAIT__返回 trait name

示例如下

<?php

trait TestTrait {

    public function testMethod() {

        echo "Class: " . __CLASS__ . PHP_EOL;

        echo "Trait: " . __TRAIT__ . PHP_EOL;

    }

}

class BaseClass {

    use TestTrait;

}

class TestClass extends BaseClass {

}

$t = new TestClass();

$t->testMethod();

//Class: BaseClass

//Trait: TestTrait

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;

调用trait方法

虽然不很明显,但是如果Trait的方法可以被定义为在普通类的静态方法,就可以被调用

实例如下

<?php 

trait Foo { 

    function bar() { 

        return 'baz'; 

    } 

} 

echo Foo::bar(),"\\n"; 

?>

小伙伴们对于traits的新特性是否熟悉了呢,希望本文能对大家有所帮助。

PHP 相关文章推荐
PHP中上传大体积文件时需要的设置
Oct 09 PHP
按上下级层次关系输出内容的PHP代码
Jul 17 PHP
php中json_decode()和json_encode()的使用方法
Jun 04 PHP
file_get_contents获取不到网页内容的解决方法
Mar 07 PHP
解析PHPExcel使用的常用说明以及把PHPExcel整合进CI框架的介绍
Jun 24 PHP
PHP中如何实现常用邮箱的基本判断
Jan 07 PHP
PHP 动态生成静态HTML页面示例代码
Jan 15 PHP
php 发送带附件邮件示例
Jan 23 PHP
PHP函数http_build_query使用详解
Aug 20 PHP
php检测mysql表是否存在的方法小结
Jul 20 PHP
PHP文件后缀不强制为.php方法
Mar 31 PHP
laravel excel 上传文件保存到本地服务器功能
Nov 14 PHP
在Windows XP下安装Apache+MySQL+PHP环境
Feb 22 #PHP
PHP+APACHE实现网址伪静态
Feb 22 #PHP
php数组键名技巧小结
Feb 17 #PHP
php使用explode()函数将字符串拆分成数组的方法
Feb 17 #PHP
php使用unset()删除数组中某个单元(键)的方法
Feb 17 #PHP
php实现两个数组相加的方法
Feb 17 #PHP
php选择排序法实现数组排序实例分析
Feb 16 #PHP
You might like
天使彦史上最神还原,性别曝光的那一刻,百万网友恋爱了
2020/03/02 国漫
THINKPHP2.0到3.0有哪些改进之处
2015/01/04 PHP
smarty模板引擎中自定义函数的方法
2015/01/22 PHP
PHP.ini安全配置检测工具pcc简单介绍
2015/07/02 PHP
PHP将身份证正反面两张照片合成一张图片的代码
2017/04/08 PHP
laravel实现分页样式替换示例代码(增加首、尾页)
2017/09/22 PHP
PHP实现唤起微信支付功能
2019/02/18 PHP
php7新特性的理解和比较总结
2019/04/14 PHP
十分钟打造AutoComplete自动完成效果代码
2009/12/26 Javascript
JavaScript 读取元素的CSS信息的代码
2010/02/07 Javascript
Javascript继承机制的设计思想分享
2011/08/28 Javascript
用示例说明filter()与find()的用法以及children()与find()的区别分析
2013/04/26 Javascript
js使用eval解析json实例与注意事项分享
2014/01/18 Javascript
深入探寻seajs的模块化与加载方式
2015/04/14 Javascript
浅谈JavaScript异常处理语句
2015/06/26 Javascript
js+html5操作sqlite数据库的方法
2016/02/02 Javascript
webpack+vue.js实现组件化详解
2016/10/12 Javascript
JS设计模式之命令模式概念与用法分析
2018/02/06 Javascript
vue中本地静态图片路径写法
2018/03/06 Javascript
通过说明与示例了解js五种设计模式
2019/06/17 Javascript
Vuex 模块化使用详解
2019/07/31 Javascript
用 js 写一个 js 解释器过程详解
2019/08/02 Javascript
vue动态绘制四分之三圆环图效果
2019/09/03 Javascript
[00:14]PWL:老朋友Mushi拍VLOG与中国玩家问好
2020/11/04 DOTA
python实现微信每日一句自动发送给喜欢的人
2019/04/29 Python
详解Python 爬取13个旅游城市,告诉你五一大家最爱去哪玩?
2019/05/07 Python
python异步编程 使用yield from过程解析
2019/09/25 Python
使用Python来做一个屏幕录制工具的操作代码
2020/01/18 Python
如果一个类实现了多个接口但是这些接口有相同的方法名将会怎样
2013/06/16 面试题
JSF的标签库有哪些
2012/04/27 面试题
库房主管岗位职责
2013/12/31 职场文书
高三政治教学反思
2014/02/06 职场文书
《画》教学反思
2014/04/14 职场文书
施工工地安全标语
2014/06/07 职场文书
2015年收银员个人工作总结
2015/04/01 职场文书
校长一岗双责责任书
2015/05/09 职场文书