PHP Trait介绍

我能用PHP Trait做什么?

如果你想要代码复用、保持了类之间的单继承或是减少代码复杂性,你就可以尝试使用一下Trait。

PHP
PHP

什么是Trait

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

引自 PHP: Trait – Manual

Trait 在底层的运行原理很简单:PHP 解释器在编译代码时会把 Trait 部分代码“复制粘贴”到类的定义体中,但是不会处理这个操作引入的不兼容问题,以此实现了代码复用、保持了类之间的单继承、减少代码复杂性。

使用样例

PHP官方手册提供了一个样例程序可以帮助我们快速理解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();

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

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

除特殊关键字以外,内部构造其实和 PHP 普通的类相同。Trait 的命名规范最好是以“able” 结尾,这样方便我们自己识别和理解。其次建议每个文件只定义一种Trait,这是良好的实践。看完上述示例可能会有这样的疑问:为什么要定义一个Trait而不是Class来做这样一件事情,在这个继承的子类中调用父类的方法不是可以达成同样的效果吗?

看看下面的例子:

<?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();

通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。当我们有大量的方法是通过组合的方式嵌入各种类和他们的子类中的时候,由于 PHP 只有单继承的原因,会使你的类显得异常臃肿,难以解读,而在使用了 Trait 之后,我们只需要提取出Trait,就可以很方便的随意组合,而且还能保证你的代码非常清晰。

实战:在Symfony4 Entity中使用Trait

  1. 首先创建一个Trait,名为Timestampable,其中有2个私有成员:createdAt和updatedAt,和4个与之相关的Getter和Setter方法。
<?php
namespace App\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
 
trait Timestampable
{
    /**
     * @ORM\Column(type="datetime")
     */
    private $createdDate;
 
    /**
     * @ORM\Column(type="datetime")
     */
    private $updatedDate;
 
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $createdBy;
 
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $updatedBy;
 
    public function getCreatedDate(): ?\DateTimeInterface
    {
        return $this->createdDate;
    }
 
    public function setCreatedDate(\DateTimeInterface $createdDate): self
    {
        $this->createdDate = $createdDate;
 
        return $this;
    }
 
    public function getCreatedBy()
    {
        return $this->createdBy;
    }
 
    public function setCreatedBy($createdBy): void
    {
        $this->createdBy = $createdBy;
    }
 
    public function getUpdatedDate(): ?\DateTimeInterface
    {
        return $this->updatedDate;
    }
 
    public function setUpdatedDate(\DateTimeInterface $updatedDate): self
    {
        $this->updatedDate = $updatedDate;
 
        return $this;
    }
 
    public function getUpdatedBy()
    {
        return $this->updatedBy;
    }
 
    public function setUpdatedBy($updatedBy): void
    {
        $this->updatedBy = $updatedBy;
    }
 
}

2. 在真实的Entity类中我们引用该Trait:

<?php
 
namespace App\Entity;
 
use Doctrine\ORM\Mapping as ORM;
use App\Entity\Traits\TimestampableTrait;
 
/**
 * @ORM\Table(name="object")
 * @ORM\Entity(repositoryClass="Blog\AppBundle\Entity\ObjectRepository")
 */
class Object
{
    use TimestampableTrait;
 
    /**
     * @ORM\Column(name="idObject" type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
 
    /* Other properties you need in your entity */
 
    /* Getters &amp; Setters */
}

当我们执行

php bin/console doctrine:migration:migrate

时,就会自动附加两个字段到我们的Entity中,由于这两个字段是常见字段,会出现在几乎所有的业务Entity中,我们就省去了大量的代码量来做这件重复的事情。

总结

Trait并不是PHP中难以理解的一个特性,相反地,它很容易理解和使用,是生成更简洁、更灵活的代码的绝佳方式。但是由于它的易用性,也应当注意不要滥用。有的时候构建一个类并继承它反而是一个更加正确的做法。

我的其他文章

分享-Sharing

关于 “PHP Trait介绍” 的 3 个意见

  1. Yesterday, I was scrolling through my feeds and saw this article.
    It could really be used on my Symfony project and now it works, thanks.

发表评论

电子邮件地址不会被公开。