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 相关文章推荐
一个oracle+PHP的查询的例子
Oct 09 PHP
支持oicq头像的留言簿(一)
Oct 09 PHP
PHP中文URL编解码(urlencode()rawurlencode()
Jul 03 PHP
php 字符串替换的方法
Jan 10 PHP
实现在同一方法中获取当前方法中新赋值的session值解决方法
Jun 26 PHP
微信公众号开发之微信公共平台消息回复类实例
Nov 14 PHP
10个php函数实用却不常见
Oct 13 PHP
给WordPress的编辑后台添加提示框的代码实例分享
Dec 25 PHP
php调用自己java程序的方法详解
May 13 PHP
php利用imagemagick实现复古老照片效果实例
Feb 16 PHP
laravel excel 上传文件保存到本地服务器功能
Nov 14 PHP
PHP7 弃用功能
Mar 09 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
javascript setTimeout和setInterval 的区别
2009/12/08 Javascript
jQuery学习笔记之jQuery的DOM操作
2010/12/22 Javascript
JavaScript学习笔记(二) js对象
2011/10/25 Javascript
javascript跑马灯悬停放大效果实现代码
2012/12/12 Javascript
javascript中文本框中输入法切换的问题
2013/12/10 Javascript
js实现iframe自动自适应高度的方法
2015/02/17 Javascript
深入浅析react native es6语法
2015/12/09 Javascript
jQuery中选择器的基础使用教程
2016/05/23 Javascript
JS实现unicode和UTF-8之间的互相转换互转
2017/07/05 Javascript
js实现本地时间同步功能
2017/08/26 Javascript
JavaScript中发出HTTP请求最常用的方法
2018/07/12 Javascript
基于Vue实现可以拖拽的树形表格实例详解
2018/10/18 Javascript
JS实现的简单tab切换功能完整示例
2019/06/20 Javascript
Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
2019/09/05 Javascript
理解JavaScript中的对象
2020/08/25 Javascript
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
[01:03:09]完美世界DOTA2联赛PWL S2 Forest vs SZ 第二场 11.25
2020/11/26 DOTA
Python(Tornado)模拟登录小米抢手机
2013/11/12 Python
修复 Django migration 时遇到的问题解决
2018/06/14 Python
详解Python字典的操作
2019/03/04 Python
值得收藏的10道python 面试题
2019/04/15 Python
Python判断字符串是否xx开始或结尾的示例
2019/08/08 Python
python爬虫 正则表达式解析
2019/09/28 Python
解决pycharm同一目录下无法import其他文件
2020/02/12 Python
Python numpy矩阵处理运算工具用法汇总
2020/07/13 Python
世界上最大的专业美容用品零售商:Sally Beauty
2017/07/02 全球购物
美国最便宜的旅游网站:CheapTickets
2017/07/09 全球购物
Servlet的实例是在生命周期什么时候创建的?配置servlet最重要的是什么?
2012/05/30 面试题
心理健康课教学反思
2014/02/13 职场文书
销售顾问岗位职责
2014/02/25 职场文书
校园安全演讲稿
2014/05/09 职场文书
天坛导游词
2015/02/02 职场文书
五一劳动节活动总结
2015/02/09 职场文书
2015年卫生局工作总结
2015/07/24 职场文书
小学数学教师研修日志
2015/11/13 职场文书
Linux中一对多配置日志服务器的详细步骤
2022/07/23 Servers