奥门新浦京官方网站Laravel中Trait的用法实例详解_php实例_脚本之家

从PHP的5.4.0版本开始,PHP提供了一种全新的代码复用的概念,那就是Trait。Trait其字面意思是”特性”、”特点”,我们可以理解为,使用Trait关键字,可以为PHP中的类添加新的特性。

奥门新浦京官方网站,本文实例讲述了PHP
Trait代码复用类与多继承实现方法。分享给大家供大家参考,具体如下:

本文实例讲述了Laravel中Trait的用法。分享给大家供大家参考,具体如下:

熟悉面向对象的都知道,软件开发中常用的代码复用有继承和多态两种方式。在PHP中,只能实现单继承。而Trait则避免了这点。下面通过简单的额例子来进行对比说明。

前言

看看PHP官方手册对Trait的定义:

1. 继承 VS 多态 VS Trait

现在有Publish.phpAnswer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案:

  • 继承
  • 多态
  • Trait

众所周知,一直以来PHP和很多语言一样是单继承的语言,但是常常在编码过程中,我们需要在当前类中使用两个或两个以上的其他类的方法,这种情况下继承就不能实现,而往往采用new方式实例化很多要用到的类,这样就会很影响代码的结构和开发规范。于是Trait类诞生了,它是一种代码复用的语法,能够实现一个类中引用多个其他类的方法。

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

1.1. 继承

如图:

奥门新浦京官方网站 1

代码结构如下:

// Log.php
<?php
Class Log
{
    public function startLog()
    {
        // echo ...
    }

    public function endLog()
    {
        // echo ...
    }
}

// Publish.php
<?php
Class Publish extends Log
{

}

// Answer.php
<?php
Class Answer extends Log
{

}

可以看到继承的确满足了要求。但这却违背了面向对象的原则。而发布(Publish)和回答(Answer)这样的操作和日志(Log)之间的关系并不是子类与父类的关系。所以不推荐这样使用。

一、概念

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

1.2. 多态

如图:

奥门新浦京官方网站 2

实现代码:

// Log.php
<?php
Interface Log
{
    public function startLog();
    public function endLog();
}

// Publish.php
<?php
Class Publish implements Log
{
    public function startLog()
    {
        // TODO: Implement startLog() method.
    }
    public function endLog()
    {
        // TODO: Implement endLog() method.
    }
}

// Answer.php
<?php
Class Answer implements Log
{
    public function startLog()
    {
        // TODO: Implement startLog() method.
    }
    public function endLog()
    {
        // TODO: Implement endLog() method.
    }
}

记录日志的操作应该都是一样的,因此,发布(Publish)和回答(Answer)动作中的日志记录实现也是一样的。很明显,这违背了DRY(Don’t
Repeat Yourself)原则。所以是不推荐这样实现的。

PHP官方手册对Trait的描述是:Trait是为类似PHP的单继承语言而准备的一种代码复用机制。Trait为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用method。Trait和Class组合的语义定义了一种减少复杂性的方式,避免传统多继承和Mixin类相关典型问题。Trait和Class相似,但仅仅旨在用细粒度和一致的方式来组合功能。无法通过trait自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个Class之间不需要继承。

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

1.3. Trait

如图:

奥门新浦京官方网站 3

实现代码如下:

// Log.php
<?php
trait Log{
    public function startLog() {
        // echo ..
    }
    public function endLog() {
        // echo ..
    }
}

// Publish.php
<?php
class Publish {
    use Log;
}
$publish = new Publish();
$publish->startLog();
$publish->endLog();

// Answer.php
<?php
class Answer {
    use Log;
}
$answer = new Answer();
$answer->startLog();
$answer->endLog();

可以看到,我们在没有增加代码复杂的情况下,实现了代码的复用。

二、Trait类的使用

官方手册也举了两个例子:

1.4. 结论

继承的方式虽然也能解决问题,但其思路违背了面向对象的原则,显得很粗暴;多态方式也可行,但不符合软件开发中的DRY原则,增加了维护成本。而Trait方式则避免了上述的不足之处,相对优雅的实现了代码的复用。

简单地讲,Trait就是一种不同于继承的语法,定义一个trait类,在其他类中使用它则是采用use关键字,有点类似于命名空间的用法,但是含义不同。use关键字在一个类中引入Trait类后,相当于require或include了一段代码进来,不同之处在于use的Trait类与当前类是可以看做同一个类的,即当前类可以用$this关键字调用Trait类的方法。

Trait用法示例

2. Trait的作用域

了解了Trait的好处,我们还需要了解其实现中的规则,先来说一下作用域。这个比较好证明,实现代码如下:

<?php
class Publish {
    use Log;
    public function doPublish() {
        $this->publicF();
        $this->protectF();
        $this->privateF();
    }
}
$publish  = new Publish();
$publish->doPublish();

执行上述代码输出结果如下:

public function
protected function
private function

可以发现,Trait的作用域在引用该Trait类的内部是都可见的。可以理解为use关键字将Trait的实现代码Copy了一份到引用该Trait的类中。

可以看出当前类可以简单地use两个Trait类,并调用其中的方法,而不仅限于继承,只能使用一个父类的方法。

Trait的优先级

3. Trait中属性的优先级

说到优先级,就必须要有一个对比的参照物,这里的参照对象时引用Trait的类及其父类。

通过以下的代码来证明Trait应用中的属性的优先级:

<?php
trait Log
{
    public function publicF()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
    protected function protectF()
    {
        echo __METHOD__ . ' protected function' . PHP_EOL;
    }
}

class Question
{
    public function publicF()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
    protected function protectF()
    {
        echo __METHOD__ . ' protected function' . PHP_EOL;
    }
}

class Publish extends Question
{
    use Log;

    public function publicF()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
    public function doPublish()
    {
        $this->publicF();
        $this->protectF();
    }
}
$publish = new Publish();
$publish->doPublish();

上述代码的输出结果如下:

Publish::publicF public function
Log::protectF protected function

通过上面的例子,可以总结出Trait应用中的优先级如下:

  1. 来自当前类的成员覆盖了 trait 的方法
  2. trait 覆盖了被继承的方法

类成员优先级为:当前类>Trait>父类

三、Trait类的访问控制

从基类继承的成员被 trait
插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而
trait 则覆盖了被继承的方法。

4. Insteadof和As关键字

在一个类中,可以引用多个Trait,如下:

<?php
trait Log
{
    public function startLog()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
    protected function endLog()
    {
        echo __METHOD__ . ' protected function' . PHP_EOL;
    }
}

trait Check
{
    public function parameterCheck($parameters) {
        // do sth
    }
}

class Publish extends Question
{
    use Log,Check;
    public function doPublish($para) {
        $this->startLog();
        $this->parameterCheck($para);
        $this->endLog();
    }
}

通过上面的方式,我们可以在一个类中引用多个Trait。引用多个Trait的时候,就容易出问题了,最常见的问题就是两个Trait中如果出现了同名的属性或者方法该怎么办呢?这个时候就需要用到Insteadof 和 as 这两个关键字了.请看如下实现代码:

<?php

trait Log
{
    public function parameterCheck($parameters)
    {
        echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
    }

    public function startLog()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
}

trait Check
{
    public function parameterCheck($parameters)
    {
        echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
    }

    public function startLog()
    {
        echo __METHOD__ . ' public function' . PHP_EOL;
    }
}

class Publish
{
    use Check, Log {
        Check::parameterCheck insteadof Log;
        Log::startLog insteadof Check;
        Check::startLog as csl;
    }

    public function doPublish()
    {
        $this->startLog();
        $this->parameterCheck('params');
        $this->csl();
    }
}

$publish = new Publish();
$publish->doPublish();

执行上述代码,输出结果如下:

Log::startLog public function
Check::parameterCheck parameter checkparams
Check::startLog public function

就如字面意思一般,insteadof关键字用前者取代了后者,as 关键字给被取代的方法起了一个别名。

在引用Trait时,使用了use关键字,use关键字也用来引用命名空间。两者的区别在于,引用Trait时是在class内部使用的。

我们知道,继承的方式,如果基类是private修饰控制的,则子类是无法调用的。但是Trait不一样,因为它类似于Require到当前类中了,所以不管是public、protected或private都是可以直接使用的。示例如下:

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

四、Trait类的优先级控制

Hello World!

Trait类与当前使用类、继承的基类之间的调用优先级顺序如下:当前使用类>Trait类>继承的基类

以上内容来自PHP官网手册。

当存在同名方法时,会根据优先级覆盖掉同名的类。具体示例如下:

Trait在Laravel中的使用

1、Trait类覆盖基类

Laravel中大量使用Trait特性来提高代码的复用性,本文只是从某个Laravel项目中举个例子。

2、当前类覆盖Trait类

比如在一个PageController.php控制器中有个show方法:

五、多个Trait类的冲突控制

public function show{ $page = PageRepository::find; $this->checkPage; return View::make('pages.show', ['page' => $page]);}

在PHP中,如果当前类use了两个Trait类,同时两个trait类都存在一个同名的方法,此时如果没有明确解决冲突将会产生一个致命错误。对于这种情况,PHP官方给出了两个解决方案:1、insteadof关键字:通过该关键字指定方法名冲突时该使用哪个Trait类的方法,即:如果C类use了A、B两个Trait类,且A、B两个类都存在a、b方法,则在C类use
A、B类时使用insteadof声明冲突的解决方法即可:

这里PageRepository::find()方法就是使用的一个Trait的方法,在PageRepository.php中使用命名空间声明及引入:

use A, B { B::a insteadof A; //a方法冲突时使用B类的a方法而不使用A类的a方法 A::b insteadof B; //b方法冲突时使用A类的b方法而不使用B类的b方法}
namespace GrahamCampbellBootstrapCMSRepositories;use GrahamCampbellCredentialsRepositoriesAbstractRepository;use GrahamCampbellCredentialsRepositoriesPaginateRepositoryTrait;use GrahamCampbellCredentialsRepositoriesSlugRepositoryTrait;class PageRepository extends AbstractRepository{ use PaginateRepositoryTrait, SlugRepositoryTrait; // 此处省略800子}

2、as关键字:通过as关键字将同名方法指定为一个别名,且仅作用于当前类中。示例如下:

其中SlugRepositoryTrait这个Trait定义了find方法:

use A, B { B::a as c; //声明B类的a方法为c,作用于该类 A::b as d; //声明A类的b方法为d,作用于该类}
trait SlugRepositoryTrait{ /** * Find an existing model by slug. * * @param string $slug * @param string[] $columns * * @return IlluminateDatabaseEloquentModel */ public function find($slug, array $columns = ['*']) { $model = $this->model; return $model::where->first; }}

六、与继承、直接实例化的区别

这样就可以在控制中使用Trait了,很好的实现了代码的复用。

对于当前一个类需要用到另一个或多个类的方法的情况,我们一般会想到的方式有继承、直接实例化另外一个或多个类等等的方法,下面来对比一下这些方法和Trait类的区别:1、继承方式:对于继承,可以完美地复用另一个类的一些方法,但是对于需要复用多个类的方法时,PHP是不支持多继承的,而且只能访问public和protected方法;2、与直接实例化的区别:我们也可以在当前类中直接实例化要用到的A类与B类,但是这种方法在控制访问范围反面,只允许访问A、B类中public的方法;3、使用Trait类则完全将A、B两个类的方法导入到当前类中,可以视为当前类的一部分,唯一区别是可以存在于当前类同名的方法,此时由优先级顺序来控制。

个人理解:

补充:PHP多继承示例

在一个类中使用Trait,就相当于这个类也有了Trait中定义的属性和方法。Traits的使用场景是如果多个类都要用到同样的属性或者方法,这个时候使用Traits可以方便的给类增加这些属性或方法,而不用每个类都去继承一个类,如果说继承类是竖向扩展一个类,那么Traits是横向扩展一个类,从而实现代码复用。

class Base{ public function sayHello(){ echo "hello "; }}trait SayWorld{ public function sayHello; echo "world".PHP_EOL; }}trait SayWorld2{ public function sayHello2(){ echo "PHP".PHP_EOL; }}class MyHelloWorld extends Base{ use SayWorld,SayWorld2;}$s = new MyHelloWorld;$s->sayHello2();

关于PHP中Trait的使用还可参考前面一篇《PHP中的traits简单使用实例》

hello worldPHP

本文转自:小谈博客

更多关于PHP相关内容感兴趣的读者可查看本站专题:《php面向对象程序设计入门教程》、《PHP数组操作技巧大全》、《PHP基本语法入门教程》、《PHP运算与运算符用法总结》、《php字符串用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

更多关于Laravel相关内容感兴趣的读者可查看本站专题:《Laravel框架入门与进阶教程》、《php优秀开发框架总结》、《smarty模板入门基础教程》、《php日期与时间用法总结》、《php面向对象程序设计入门教程》、《php字符串用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

希望本文所述对大家PHP程序设计有所帮助。

希望本文所述对大家基于Laravel框架的PHP程序设计有所帮助。

发表评论

电子邮件地址不会被公开。 必填项已用*标注