PHP特质方法冲突:特质“继承”和特质层次结构

28

更新: 在我思考这个问题的过程中,发现我并不孤单,实际上这似乎是一个bug。请参见此处。它被修复的那一天将会是美好的一天!:)


一开始我的想法是“我喜欢PHP traits!我要无处不用它们!^_^”,但现在这已经成为了一个“思考练习/学习体验 >_<”。

考虑以下例子:

trait TheErrorOfYourWays{

   public function booboo(){
       echo 'You had a booboo :(';
   }
}

trait SpectacularStuff1 {
    use TheErrorOfYourWays; 
}

trait SpectacularStuff2 {
    use TheErrorOfYourWays;
}

class DoSomethingSpectacular {
    use SpectacularStuff1, SpectacularStuff2;
}

这导致一个(显然不太显然的)错误:

  

致命错误:因为在DoSomethingSpectacular上有与其他trait方法冲突,所以未应用booboo trait方法。

我的问题是:如何解决traits中的方法冲突?是否可以实现重叠的trait“继承”?如果可以,正确的方法是什么?

我为什么要这样做:

  1. 我想创建自包含的traits和类(混合和匹配风格)。 如果可能的话,我想说“使用”,然后必须发生魔法。不需要抓头想,“这个trait在哪个命名空间中?”等等。
  2. 当我做一些“冒险”并发现我无意中创建了冲突时,不需要在代码中即时修改类和traits。
  3. 看起来好像是个好主意。

我尝试过的方法:

  1. PHP手册。
  2. Google搜索。
  3. 在SO上查找,包括这个问题 -> 不适用于此情况的答案。
  4. 找到了这个,但我正在使用PHP版本5.5.1。已修复,对吗?对吗?
  5. 在不同的位置、时间、宇宙中使用“as”、“alias”甚至是“insteadof”的奇妙组合,包括但不限于:

    trait SpectacularStuff1 {
       use TheErrorOfYourWays{
          TheErrorOfYourWays::booboo as booboo1;
       }
    }
    trait SpectacularStuff2 {
       use TheErrorOfYourWays{
          TheErrorOfYourWays::booboo as booboo2;
       }
    }
    class DoSomethingSpectacular {
       use SpectacularStuff1, SpectacularStuff2 {
          /* Tried separately, but included here for brevity's sake */
          SpectacularStuff1::booboo as booboo3;
          SpectacularStuff2::booboo as booboo4;
       }
    }
    

    use TheErrorOfYourWays as Erroneous1;
    trait SpectacularStuff1 {
        use Erroneous1{
            Erroneous1::booboo as booboo1;
        }
    }
    
    use TheErrorOfYourWays as Erroneous2;
    trait SpectacularStuff2 {
        use Erroneous2{
            Erroneous2::booboo as booboo2;
        }
    }
    

我明白:

  1. 我可以将TheErrorOfYourWays更改为类并使booboo()静态化,但我想了解这种特定的trait行为。
  2. 我可以从traits中删除TheErrorOfYourWays并在类中使用它,但那几乎不是“自包含”的。每次我使用traits时,我都必须记得在类中使用TheErrorOfYourWays,即使我没有直接从类中调用booboo()。听起来很危险。
  3. 我可能犯了一些新手语法错误或者在别名方面理解有误。如果是这样,请……慢慢地解释一下……
  4. 可能有更好的方法来解决这个问题。如果是这样,请……慢慢地解释一下……
  5. 我可能过早地热情了,PHP目前还不能实现这个。请温柔地让我意识到这一点。

谢谢!

6个回答

23

要解决Traits中的冲突,您需要使用关键字insteadof

源码

重写您的

class DoSomethingSpectacular {
   use SpectacularStuff1, SpectacularStuff2 {
      /* Tried separately, but included here for brevity's sake */
      SpectacularStuff1::booboo as booboo3;
      SpectacularStuff2::booboo as booboo4;
   }
}

class DoSomethingSpectacular {
    use SpectacularStuff1, SpectacularStuff2 
    {
     SpectacularStuff1::booboo insteadof SpectacularStuff2;
     SpectacularStuff2::booboo insteadof SpectacularStuff1;
    }
}

将解决冲突。


如问题所述,我已经尝试过了,但是还是出现了错误。请提供一个可行的代码示例,因为可能是我没有正确实现它。谢谢 :) - user651390
嗯...好的,感谢你教我正确使用insteadof。我之前不知道 :). 但是这仍然没有解决trait层面的冲突。它要求我知道哪些traits和classes调用相同的traits,这可能会导致非常长的insteadof列表。还是有什么地方我还没注意到吗? - user651390
我认为是这样的(你需要一个很长的列表 :( ),顺便问一下,你有看过我提供的链接中的“示例#5:冲突解决”吗? - Shankar Narayana Damodaran
我看到了#5并尝试了,但它没有给我想要的结果。在来这里之前,我仔细研究了手册,相信我;) 这个“长列表”新闻让我很难过:(。 - user651390
1
非常感谢您的帮助!非常感谢! - user651390

5

因此,非官方的“官方”答案是:

你可以不使用别名、insteadof或任何东西来实现!但目前还不行...

我从5.5.1升级到5.5.6,但一切都是徒劳的。当修复程序可用时,我将更新此答案。有趣的是,你可以直接调用trait静态函数。以下示例有效:

trait TheErrorOfYourWays{
    public static function booboo($thisTrait){
        echo 'You had a booboo :( in '.$thisTrait.'<br>';
    }
}

trait SpectacularStuff1 {
    public function boobooTest1(){
        TheErrorOfYourWays::booboo(__TRAIT__);
    }
}

trait SpectacularStuff2 {
    public function boobooTest2(){
        TheErrorOfYourWays::booboo(__TRAIT__);
    }
}

class DoSomethingSpectacular {
    use SpectacularStuff1, SpectacularStuff2;
}

$boobooAChoo = new DoSomethingSpectacular();
$boobooAChoo->boobooTest1(); // You had a booboo :( in SpectacularStuff1
$boobooAChoo->boobooTest2(); // You had a booboo :( in SpectacularStuff2

是的,你当然也可以使用类来实现这个功能,但是现在类已经过时了。


1
我找到了另一种临时修复方法:

trait A {
   public function foo(){
       echo 'foo';
   }
}

trait B {
   public function foofoo(){
       return $this->foo () . 'foo';
   }
}

trait C {
   public function foobar(){
       return $this->foo () . 'bar';
   }
}

class DoSomethingSpectacular {
    use A, B, C;

    public function foobarfoofoo () {
        echo $this->foobar () . $this->foofoo ();
    }
}

它可以正常工作 :)


有趣...只需要记得始终使用A ;-) - user651390

0

这在 PHP 7.3 中已经被修复3v4l.org/qulMv。这意味着,在两个或更多派生特征中使用一个或多个基本特征,然后在类中使用派生特征不再是问题!

请注意,不幸的是,它没有在PHP 7.3迁移指南中记录,但它存在于changelog中(相同的特征方法在组合期间会引发错误)。


0

一种实现方式是使用抽象方法。我称其为“扁平化Trait”或“Trait依赖注入”。这更多地是一个通用的解决方案,而非具体的。

你的示例可以改为:

trait TheErrorOfYourWays
{
    public function booboo(): string
    {
        return 'booboo';
    }
}

trait SpectacularStuff1
{
    abstract public function booboo(): string;
}

trait SpectacularStuff2
{
    abstract public function booboo(): string;
}

class DoSomethingSpectacular
{
    use TheErrorOfYourWays;
    use SpectacularStuff1;
    use SpectacularStuff2;
}

这就是我个人认为特质变得强大的地方:您可以编写完全独立的特质并在任何地方使用它们。换句话说,它们可以是完全独立的。这就是让我喜欢特质(或至少是 PHP 特质)的原因。

虽然我更喜欢以这种方式使用特质,但可能有用例需要使特质彼此依赖。例如,将一些相关的特质分组成一个大特质。


0
一种小技巧,只需在类 DoSomethingSpectacular 中添加函数 booboo 即可。
<?php
trait TheErrorOfYourWays{

   public function booboo(){
       echo 'You had a booboo :(';
   }
}

trait SpectacularStuff1 {
   use TheErrorOfYourWays{
      TheErrorOfYourWays::booboo as booboo1;
   }
}

trait SpectacularStuff2 {
   use TheErrorOfYourWays{
      TheErrorOfYourWays::booboo as booboo2;
   }
}
class DoSomethingSpectacular {
   use SpectacularStuff1, SpectacularStuff2 {
      /* Tried separately, but included here for brevity's sake */
      SpectacularStuff1::booboo as booboo3;
      SpectacularStuff2::booboo as booboo4;
   }

  //very ugly hack
  public function booboo() {}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接