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实现的功能是显示8条基色色带
Oct 09 PHP
让你的WINDOWS同时支持MYSQL4,MYSQL4.1,MYSQL5X
Dec 06 PHP
分享PHP入门的学习方法
Jan 02 PHP
php 什么是PEAR?(第二篇)
Mar 19 PHP
PHP隐形一句话后门,和ThinkPHP框架加密码程序(base64_decode)
Nov 02 PHP
用php代码限制国内IP访问我们网站
Sep 26 PHP
使用PHP+AJAX让WordPress动态加载文章的教程
Dec 11 PHP
PHP实现的oracle分页函数实例
Jan 25 PHP
PHP实现二维数组去重功能示例
Jan 12 PHP
PHP基于回溯算法解决n皇后问题的方法示例
Nov 07 PHP
php生成word并下载代码实例
Mar 15 PHP
thinkphp5.1框架模板布局与模板继承用法分析
Jul 19 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
PHP+ajax分页实例简析
2015/12/07 PHP
laravel 实现向公共模板中传值 (view composer)
2019/10/22 PHP
解放web程序员的输入验证
2006/10/06 Javascript
JS比较2个日期间隔的示例代码
2014/04/15 Javascript
javascript自定义右键弹出菜单实现方法
2015/05/25 Javascript
js实现固定显示区域内自动缩放图片的方法
2015/07/18 Javascript
JS三级可折叠菜单实现方法
2016/02/29 Javascript
Javascript数组Array基础介绍
2016/03/13 Javascript
JS传递对象数组为参数给后端,后端获取的实例代码
2016/06/28 Javascript
DropDownList实现可输入可选择(两种版本可选)
2016/12/07 Javascript
react.js 翻页插件实例代码
2017/01/19 Javascript
react.js CMS 删除功能的实现方法
2017/04/17 Javascript
jQuery使用JSONP实现跨域获取数据的三种方法详解
2017/05/04 jQuery
jQuery插件artDialog.js使用与关闭方法示例
2017/10/09 jQuery
jQuery实现checkbox全选功能完整实例
2018/07/12 jQuery
ajaxfileupload.js实现上传文件功能
2019/04/19 Javascript
JavaScript 面向对象程序设计详解【类的创建、实例对象、构造函数、原型等】
2020/05/12 Javascript
简单了解前端渐进式框架VUE
2020/07/20 Javascript
浅谈js数组splice删除某个元素爬坑
2020/10/14 Javascript
[57:50]DOTA2上海特级锦标赛主赛事日 - 4 胜者组决赛Secret VS Liquid第二局
2016/03/05 DOTA
Python读取Pickle文件信息并计算与当前时间间隔的方法分析
2019/01/30 Python
将python2.7添加进64位系统的注册表方式
2019/11/20 Python
python用opencv完成图像分割并进行目标物的提取
2020/05/25 Python
python 基于opencv 实现一个鼠标绘图小程序
2020/12/11 Python
小女主人连衣裙:Little Mistress
2017/07/10 全球购物
最耐用行李箱,一箱永流传:Briggs & Riley(全球终身保修)
2017/12/07 全球购物
酒店服务实习自我鉴定
2013/09/22 职场文书
企业宣传方案
2014/03/04 职场文书
安全生产月标语
2014/10/07 职场文书
光棍节联谊晚会活动策划书
2014/10/10 职场文书
5.12护士节活动总结
2015/02/10 职场文书
如何用PHP实现分布算法之一致性哈希算法
2021/05/26 PHP
java设计模式--七大原则详解
2021/07/21 Java/Android
python实现层次聚类的方法
2021/11/01 Python
centos环境下nginx高可用集群的搭建指南
2022/07/23 Servers
HTML5页面打开微信小程序功能实现
2022/09/23 HTML / CSS