PHP Traits

时间:2018-11-15 15:16:08  来源:igfitidea点击:

在本教程中,我们将学习如何使用PHP Traits在位于不同继承层次结构的独立类之间共享功能。

PHP traits的介绍

代码复用是面向对象编程最重要的方面之一。
在PHP中,可以使用继承在共享相同继承层次结构的不同类中重用代码。
但是要实现代码复用,需要将类的公共功能移到父类的方法中。继承使得代码耦合非常紧密,因此很难维护代码。

为了克服这个问题,从5.4.0版本开始,PHP引入了一个新的代码重用单元,名为traitTraits允许您在许多不同的类中自由地重用一组方法,这些类不需要在同一个类层次结构中。

Trait和class类似,但它只用于以细粒度和一致的方式对方法进行分组。不允许单独实例化一个trait

PHP traits 示例

声明一个新的trait 和声明一个新的类类似,如下面的例子所示:

<?php

trait Logger {

    function log($msg) {
        echo '<pre>';
        echo date('Y-m-d h:i:s') . ':' . '(' . __CLASS__ . ') ' . $msg . '<br/>';
        echo '</pre>';
    }

}

要在类中使用trait,可以使用use关键字。

trait的所有方法在使用它的类中都是可用的。调用trait的方法类似于调用实例方法。

下面的例子演示了如何在BankAccount类中使用trait Logger:

class BankAccount{
    use Logger;
    
    private $accountNumber;
    
    function __construct($accountNumber){
        $this->accountNumber = $accountNumber;
        $this->log("创建一个新的银行帐号 $accountNumber");
    }
}

我们也可以在User类中复用Logger trait,如下:

class User{
    use Logger;
    function __construct() {
        $this->log("创建了一个新用户");
    }
}

BankAccount和User类都重用了Logger trait的方法,这是非常灵活的。

我们可以测试一下我们的脚本,看它是否工作。

$user = new User();
$account = new BankAccount('123546');

使用多个trait

一个类可以使用多个trait。

下面的例子演示了如何在IDE类中使用多个trait。为了便于演示,它在PHP中模拟了C编译模型。

<?php

trait Preprocessor {

    function preprocess() {
        echo '预处理…完成' . '<br/>';
    }

}

trait Compiler {

    function compile() {
        echo '编译代码…完成' . '<br/>';
    }

}

trait Assembler {

    function createObjCode() {
        echo '创建目标代码文件…完成' . '<br/>';
    }

}

trait Linker {

    function createExec() {
        echo '创建可执行文件…完成' . '<br/>';
    }

}

class IDE {

    use Preprocessor,
        Compiler,
        Assembler,
        Linker;

    function run() {
        $this->preprocess();
        $this->compile();
        $this->createObjCode();
        $this->createExec();

        echo '执行文件…完成' . '<br/>';
    }

}

$ide = new IDE();
$ide->run();

组合多个trait

通过在trait声明中使用use语句,可以由其他trait组成一个新的trait。请看下面的例子:

<?php

trait Reader {

    public function read($source) {
        echo sprintf("Read from %s <br/>", $source);
    }

}

trait Writer {

    public function write($destination) {
        echo sprintf("Write to %s <br/>", $destination);
    }

}

trait Copier {

    use Reader,
        Writer;

    public function copy($source, $destination) {
        $this->read($source);
        $this->write($destination);
    }

}

class FileUtil {

    use Copier;

    public function copyFile($source, $destination) {
        $this->copy($source, $destination);
    }

}

首先,我们创建了Reader和Writer trait。
其次,我们创建了一个新是Copier trait,它由Reader和Writer trait组成。
在Copier trait的copy()方法中,我们调用了Reader中的read()方法和Writer trait的write()方法。
最后,我们使用FileUtil类的Copier trait中的copyFile()方法来模拟复制文件操作。

解决PHP trait的方法名冲突问题

重载(覆盖)trait方法

如果一个类使用了多个有相同方法名的trait,将会引发PHP致命错误。
幸运的是,我们可以通过inteadof关键字来告诉PHP使用哪个trait的哪个方法。

让我们看看下面的例子:

<?php

trait FileLogger {

    public function log($msg) {
        echo '文件日志记录 ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>';
    }

}

trait DatabaseLogger {

    public function log($msg) {
        echo '数据库日志记录 ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>';
    }

}

class Logger {

    use FileLogger,
        DatabaseLogger {
        FileLogger::log insteadof DatabaseLogger;
    }
}

$logger = new Logger();
$logger->log(' 测试消息 #1');
$logger->log(' 测试消息 #2');

FileLogger和DatabaseLogger trait都具有相同的log()方法。
在Logger类中,我们通过这种方式来解决方法名称冲突的问题:指定了将使用FileLogger trait的log()方法而不是使用DatabaseLogger的log()方法。

如果我们想同时使用FileLogger和DatabaseLogger trait中的方法,该怎么办呢?
我们可以使用alias为trait的方法起别名。

为trait方法起别名

通过对多个trait中相同的方法名使用别名,您可以重用这些trait中的所有方法。
您可以使用as关键字将trait的方法名改成另一个名称。

下面的例子说明了如何使用别名方法解决trait中的方法名冲突问题:

class Logger{
    use FileLogger, DatabaseLogger{
    DatabaseLogger::log as log2DB;
    FileLogger::log insteadof DatabaseLogger;
    }
}
 
$logger = new Logger();
$logger->log('测试消息 #1');
$logger->log2DB('测试消息 #2');

DatabaseLogger类的方法log()在Logger类的上下文中有了一个新名字(log2DB)。