在PHP 5.4中使用特性

Minimizing code duplication through better organization and code reuse is an important goal of Object Oriented Programming. But in PHP it can sometimes be difficult because of the limitations of the single inheritance model it uses; you might have some methods that you would like to use in multiple classes but they may not fit well into the inheritance hierarchy.

通过更好的组织和代码重用来最大程度地减少代码重复是面向对象编程的重要目标。 但是在PHP中,由于使用的单一继承模型的局限性,有时可能会很困难。 您可能有一些要在多个类中使用的方法,但它们可能不太适合继承层次结构。

Languages like C++ and Python allow us to inherit from multiple classes which solves this problem to some extent, and mixins in Ruby allows us to mix the functionality of one or more classes without using inheritance. But multiple inheritance has issues such as the Diamond Problem problem, and mixins can be a complex mechanism to work with.

诸如C ++和Python之类的语言允许我们从多个类继承,这在某种程度上解决了这一问题,而Ruby中的mixins允许我们混合一个或多个类的功能而无需使用继承。 但是多重继承会带来诸如钻石问题(Diamond Problem Problem)之类的问题,而混合是一个复杂的机制。

In this article I will discuss traits, a new feature introduced in PHP 5.4 to overcome such issues. The concept of traits itself is nothing new to programming and is used in other languages like Scala and Perl. They allows us to horizontally reuse code across independent classes in different class hierarchies.

在本文中,我将讨论traits,这是PHP 5.4中引入的一项新功能,可以克服此类问题。 特质的概念本身对编程而言并不是什么新鲜事物,并且在其他语言(例如Scala和Perl)中使用。 它们使我们能够在不同类层次结构中的各个独立类之间水平重用代码。

特质是什么样子 (What a Trait Looks Like)

A trait is similar to an abstract class which cannot be instantiated on its own (though more often it’s compared to an interface). The PHP documentation defines traits as follows:

特质类似于不能单独实例化的抽象类(尽管通常将其与接口进行比较)。 PHP文档将特征定义如下:

Traits is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.

特性是一种在PHP等单一继承语言中重用代码的机制。 特性旨在通过使开发人员在生活在不同类层次结构中的几个独立类中自由重用方法集,从而减少单一继承的某些限制。

Let’s consider this example:

让我们考虑这个例子:

<?php
class DbReader extends Mysqli
{
}

class FileReader extends SplFileObject
{
}

It’d be a problem if both classes needed some common functionality, for example making both of them singletons. Since PHP doesn’t support multiple inheritance, either each class will have to implement the necessary code to support the Singleton pattern or there will be an inheritance hierarchy that doesn’t make sense. Traits offer a solution to exactly this type of problem.

如果两个类都需要一些通用功能,例如使它们都成为单例,那将是一个问题。 由于PHP不支持多重继承,因此每个类都必须实现必要的代码以支持Singleton模式,否则将存在没有意义的继承层次结构。 性状为此类问题提供了解决方案。

<?php
trait Singleton
{
    private static $instance;

    public static function getInstance() {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self;
        }
        return self::$instance;
    }
}

class DbReader extends ArrayObject
{
    use Singleton;
}

class  FileReader
{
    use Singleton;
}

The trait Singleton has a straight forward implementation of the Singleton pattern with a static method getInstance() which creates an object of the class using this trait (if it’s not already created) and returns it.

特征Singleton具有静态方法getInstance()的Singleton模式的直接实现,该方法使用此特征创建该类的对象(如果尚未创建)并返回它。

Let’s try to create the objects of these classes using the method getInstance().

让我们尝试使用getInstance()方法创建这些类的对象。

<?php
$a = DbReader::getInstance();
$b = FileReader::getInstance();
var_dump($a);  //object(DbReader)
var_dump($b);  //object(FileReader)

We can see that $a is an object of DbReader and $b is an object of FileReader, but both are now behaving as singletons. The method from Singleton has been horizontally injected to the classes using it.

我们可以看到$aDbReader的对象,而$bFileReader的对象,但是两者现在都表现为单例。 Singleton的方法已使用该方法水平注入到类中。

Traits do not impose any additional semantics on the class. In a way, you can think of it as a compiler-assisted copy and paste mechanism where the methods of the trait is copied into the composing class.

特性不会在类上施加任何其他语义。 从某种意义上讲,您可以将其视为编译器辅助的复制和粘贴机制,其中特征的方法被复制到组成类中。

If we were simply subclassing DbReader from a parent with a private $instance property, the property wouldn’t be shown in the dump of ReflectionClass::export(). And yet with traits, there it is!

如果我们只是从具有私有$instance属性的父级DbReader子类,则该属性将不会显示在ReflectionClass::export()的转储中。 然而,具有特质!

Class [  class FileReader ] {
  @@ /home/shameer/workplace/php54/index.php 19-22

  - Constants [0] {
  }
  - Static properties [1] {
    Property [ private static $_instance ]
  }
  - Static methods [1] {
    Method [  static public method instance ] {
      @@ /home/shameer/workplace/php54/index.php 6 - 11
    }
  }
  - Properties [0] {
  }
  - Methods [0] {
  }
}

多重特征 (Multiple Traits)

So far we have used only one trait with a class, but in some cases we may need to incorporate the functionality of more than one trait.

到目前为止,我们仅在一个类中使用了一个特征,但是在某些情况下,我们可能需要合并多个特征的功能。

<?php
trait Hello
{
    function sayHello() {
        echo "Hello";
    }
}

trait World
{
    function sayWorld() {
        echo "World";
    }
}

class MyWorld
{
    use Hello, World;
}

$world = new MyWorld();
echo $world->sayHello() . " " . $world->sayWorld(); //Hello World

Here we have two traits, Hello and World. Trait Hello is only able to say “Hello” and trait World can say “World”. In the MyWorld class we have applied Hello and World so that the MyWorld object will have methods from both traits and be able to say “Hello World”.

在这里,我们有两个特征, HelloWorld 。 特质Hello只能说“你好”,特质World可以说“世界”。 在MyWorld类中,我们应用了HelloWorld这样MyWorld对象将具有来自两个特征的方法,并且能够说“ Hello World”。

特质构成的特质 (Traits Composed of Traits)

As the application grows, it’s quite possible that we will have a set of traits which are used across different classes. PHP 5.4 allows us to have traits composed of other traits so that we can include only one instead of a number of traits in all these classes. This lets us rewrite the previous example as follows:

随着应用程序的增长,我们很有可能会在不同的类中使用一组特征。 PHP 5.4允许我们具有由其他特征组成的特征,因此在所有这些类中只能包含一个特征,而不是多个特征。 这使我们可以如下重写前一个示例:

<?php
trait HelloWorld
{
    use Hello, World;
}

class MyWorld
{
    use HelloWorld;
}

$world = new MyWorld();
echo $world->sayHello() . " " . $world->sayWorld(); //Hello World

Here we have created the trait HelloWorld, using traits Hello and World, and included it in MyWorld. Since the HelloWorld trait has methods from the other two traits, it’s just the same as if we had including the two traits in the class ourselves.

在这里,我们使用特征Hello和World创建了特征HelloWorld ,并将其包含在MyWorld 。 由于HelloWorld特征具有其他两个特征的方法,因此就像我们自己在类中包含这两个特征一样。

优先顺序 (Precedence Order)

As I’ve already mentioned, traits work as if their methods have been copied and pasted into the classes using them and they are totally flattened into the classes’ definition. There may be methods with the same name in different traits or in the class itself. You might wonder which one will be available in the object of child class.

正如我已经提到的那样,特质的工作方式就像使用它们将其方法复制并粘贴到类中一样,并且将它们完全压平到类的定义中。 在不同的特性或类本身中,可能存在具有相同名称的方法。 您可能想知道子类中的哪一个可用。

The precedence order is:

优先顺序为:

  1. the methods of a trait override inherited methods from the parent class

    特征的方法覆盖从父类继承的方法
  2. the methods defined in the current class override methods from a trait

    当前类中定义的方法会覆盖特征中的方法

This is made clear in the following example:

在以下示例中将对此进行明确说明:

<?php
trait Hello
{
    function sayHello() {
        return "Hello";
    }

    function sayWorld() {
        return "Trait World";
    }

    function sayHelloWorld() {
        echo $this->sayHello() . " " . $this->sayWorld();
    }

    function sayBaseWorld() {
        echo $this->sayHello() . " " . parent::sayWorld();
    }
}

class Base
{
    function sayWorld(){
        return "Base World";
    }
}

class HelloWorld extends Base
{
    use Hello;
    function sayWorld() {
        return "World";
    }
}

$h =  new HelloWorld();
$h->sayHelloWorld(); // Hello World
$h->sayBaseWorld(); // Hello Base World

We have a HelloWorld class derived from Base, and both classes have a method named sayWorld() but with different implementations. Also, we have included the trait Hello in the HelloWorld class.

我们有一个从Base派生的HelloWorld类,两个类都有一个名为sayWorld()的方法,但实现方式不同。 另外,我们在HelloWorld类中包含了特性Hello

We have two methods, sayHelloWorld() and sayBaseWorld(), the former of which calls sayWorld() which exists in both classes as well as in the trait. But in the output, we can see the one from the child class was invoked. If we need to reference the method from the parent class, we can do so by using the parent keyword as shown in the sayBaseWorld() method.

我们有两个方法, sayHelloWorld()sayBaseWorld() ,前者调用的是sayWorld() ,这两个类都存在于特征中。 但是在输出中,我们可以看到子类中的一个被调用了。 如果我们需要从父类中引用该方法,则可以使用sayBaseWorld()方法中所示的parent关键字来实现。

解决冲突和混淆 (Conflict Resolution and Aliasing)

When using multiple traits there may be a situation where different traits use the same method names. For example, PHP will give a fatal error if you try to run the following code because of conflicting method names:

使用多个特征时,可能会出现不同特征使用相同方法名称的情况。 例如,如果由于方法名称冲突而尝试运行以下代码,PHP将给出致命错误:

<?php
trait Game
{
    function play() {
        echo "Playing a game";
    }
}

trait Music
{
    function play() {
        echo "Playing music";
    }
}

class Player
{
    use Game, Music;
}

$player = new Player();
$player->play();

Such trait conflicts aren’t resolved automatically for you. Instead, you must choose which method should be used inside the composing class using the keyword insteadof.

无法自动为您解决此类特征冲突。 相反,您必须使用关键字代替代替选择在组成类内使用的insteadof

<?php
class Player
{
    use Game, Music {
        Music::play insteadof Game;
    }
}

$player = new Player();
$player->play(); //Playing music

Here we have chosen to use the play() method of the Music trait inside the composing class so the class Player will play music, not a game.

在这里,我们选择在组成类中使用Music trait的play()方法,以便Player类将播放音乐,而不是游戏。

In the above example, one method has been chosen over the other from two traits. In some cases you may want to keep both of them, but still avoiding conflicts. It’s possible to introduce a new name for a method in a trait as an alias. An alias doesn’t rename the method, but offers an alternate name by which it can be invoked. Aliases are created using the keyword as.

在上面的示例中,从两个特征中选择了一种方法,而不是另一种。 在某些情况下,您可能希望保留两者,但仍要避免冲突。 可以在特征中为别名引入方法的新名称。 别名不会重命名该方法,但会提供一个备用名称,通过该别名可以调用该方法。 使用关键字as创建别名。

<?php
class Player
{
    use Game, Music {
        Game::play as gamePlay;
        Music::play insteadof Game;
    }
}

$player = new Player();
$player->play(); //Playing music
$player->gamePlay(); //Playing a game

Now any object of class Player will have a method gamePlay(), which is the same as Game::play(). Here it should be noted that we have resolved any conflicts explicitly, even after aliasing.

现在, Player类的任何对象都将具有gamePlay()方法,该方法与Game::play() 。 在这里应该指出,即使在混叠之后,我们也已经明确解决了所有冲突。

反射 (Reflection)

The Reflection API is one of the powerful features of PHP to analyze the internal structure of interfaces, classes, and methods and reverse engineer them. And since we’re talking about traits, you might be interested to know about the Reflection API’s support for traits. In PHP 5.4, four methods have been added to ReflectionClass to get information about traits in a class.

Reflection API是PHP的强大功能之一,可以分析接口,类和方法的内部结构并对它们进行反向工程。 而且,由于我们在谈论特征,所以您可能有兴趣了解Reflection API对特征的支持。 在PHP 5.4中,四个方法已添加到ReflectionClass以获取有关类中特征的信息。

We can use ReflectionClass::getTraits() to get an array of all traits used in a class. The ReflectionClass::getTraitNames() method will simply return an array of trait names in that class. ReflectionClass::isTrait() is helpful to check if something is a trait or not.

我们可以使用ReflectionClass::getTraits()来获取类中使用的所有特征的数组。 ReflectionClass::getTraitNames()方法将仅返回该类中的特征名称数组。 ReflectionClass::isTrait()有助于检查某物是否为特征。

In the previous section we discussed having aliases for traits to avoid collisions due to traits with the same name. ReflectionClass::getTraitAliases() will return an array of trait aliases mapped to its original name.

在上一节中,我们讨论了为特征使用别名,以避免因具有相同名称的特征而造成冲突。 ReflectionClass::getTraitAliases()将返回一个映射到其原始名称的特征别名数组。

其他特性 (Other Features)

Apart from the above mentioned, there are other features that makes traits more interesting. We know that in classical inheritance the private properties of a class can’t be accessed by child classes. Traits can access the private properties or methods of the composing classes, and vice versa! Here is an example:

除了上面提到的以外,还有其他一些特性使特质更加有趣。 我们知道,在经典继承中,子类无法访问类的私有属性。 特性可以访问组成类的私有属性或方法,反之亦然! 这是一个例子:

<?php
trait Message
{
    function alert() {
        echo $this->message;
    }
}

class Messenger
{
    use Message;
    private $message = "This is a message";
}

$messenger = new Messenger;
$messenger->alert(); //This is a message

As traits are completely flattened into the class composed of them, any property or method of the trait will become a part of that class and we access them just like any other class properties or methods.

当特征完全平整到由它们组成的类中时,该特征的任何属性或方法都将成为该类的一部分,我们就像访问其他任何类的属性或方法一样访问它们。

We can even have abstract methods in a trait to enforce the composing class to implement these methods. For example:

我们甚至可以在特征中使用抽象方法来强制组成类以实现这些方法。 例如:

<?php
trait Message
{
    private $message;

    function alert() {
        $this->define();
        echo $this->message;
    }
    abstract function define();
}

class Messenger
{
    use Message;
    function define() {
        $this->message = "Custom Message";
    }
}

$messenger = new Messenger;
$messenger->alert(); //Custom Message

Here we have a trait Message with an abstract method define(). It requires all classes which use this trait to implement the method. Otherwise, PHP will give an error saying there is an abstract method which has not been implemented.

在这里,我们有一个特征Message ,它带有抽象方法define() 。 它要求所有使用此特征的类来实现该方法。 否则,PHP将给出错误消息,提示存在尚未实现的抽象方法。

Unlike traits in Scala, traits in PHP can have a constructor but it must be declared public (an error will be thrown if is private or protected). Anyway, be cautious when using constructors in traits, though, because it may lead to unintended collisions in the composing classes.

与Scala中的特征不同,PHP中的特征可以具有构造函数,但必须将其声明为public(如果为private或protected,则将引发错误)。 无论如何,在特征中使用构造函数时要小心,因为它可能导致在编写类中发生意外冲突。

摘要 (Summary)

Traits are one of the most powerful features introduced in PHP 5.4, and I’ve discussed almost all their features in this article. They let programmers reuse code fragments horizontally across multiple classes which do not have to be within the same inheritance hierarchy. Instead of having complex semantics, they provide us with a light weight mechanism for code reuse. Though there are some drawbacks with traits, they certainly can help improve the design of your application removing code duplication and making it more DRY.

特性是PHP 5.4中引入的最强大的功能之一,在本文中我已经讨论了几乎所有的功能。 它们使程序员可以在多个类中水平地重用代码片段,而不必在同一继承层次结构中。 它们没有复杂的语义,而是为我们提供了轻量级的代码重用机制。 尽管特征有一些缺点,但它们无疑可以帮助改进应用程序的设计,从而消除代码重复并使代码更干燥。

Image via Vlue / Shutterstock

图片来自Vlue / Shutterstock

翻译自: https://www.sitepoint.com/using-traits-in-php-5-4/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值