PHP Traits
在本教程中,我们将学习如何使用PHP Traits在位于不同继承层次结构的独立类之间共享功能。
PHP traits的介绍
代码复用是面向对象编程最重要的方面之一。
在PHP中,可以使用继承在共享相同继承层次结构的不同类中重用代码。
但是要实现代码复用,需要将类的公共功能移到父类的方法中。继承使得代码耦合非常紧密,因此很难维护代码。
为了克服这个问题,从5.4.0版本开始,PHP引入了一个新的代码重用单元,名为trait。Traits允许您在许多不同的类中自由地重用一组方法,这些类不需要在同一个类层次结构中。
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)。

