문제

UPDATE: I am not alone in my pondering on this issue and it seems it is indeed a bug. See here. The day it is fixed is going to be a fantastic day! :)


This started out as I love PHP traits! I'm going to use them everywhere! ^_^ and now it has turned into a Thought Exercise / Learning Experience >_<.

Consider the following example:

trait TheErrorOfYourWays{

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

trait SpectacularStuff1 {
    use TheErrorOfYourWays; 
}

trait SpectacularStuff2 {
    use TheErrorOfYourWays;
}

class DoSomethingSpectacular {
    use SpectacularStuff1, SpectacularStuff2;
}

This results in (obviously not so obviously):

Fatal error: Trait method booboo has not been applied, because there are collisions with other trait methods on DoSomethingSpectacular.

So my question: How do I resolve method conflicts in traits? Is it possible to achieve overlapping trait "inheritance"? If so, what is the "right" way to do this?

Why I want to do this:

  1. I want to create self contained traits and classes (mix and match style). If it is at all possible, I want to say "use" and then magic stuff must happen. No having to scratch my head and think, "Now what namespace was that trait in again?", etc, etc.
  2. No having to edit classes and traits on the fly when I do something "adventurous" and discover I have inadvertently created a conflict.
  3. Seemed like a good idea at the time.

What I have tried:

  1. The PHP Manual.
  2. The Google.
  3. SO including this question -> Not the correct answer for this scenario.
  4. Found this but I am using PHP version 5.5.1. It's fixed, right? Right?
  5. A fantastic array of "as", aliases, even insteadof, in different places, times, universes, etc. Including, but not limited to:

    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;
       }
    }
    

    AND

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

I understand that:

  1. I can change TheErrorOfYourWays to a class and make booboo() static but I would like to learn about this specific trait behaviour.
  2. I can remove TheErrorOfYourWays from the traits and use it in the class, but that's hardly "self-contained" then. Everytime I use the traits I have to remember to use TheErrorOfYourWays in the class even if I don't call booboo() directly from the class. Sounds dangerous.
  3. I have probably made some rookie syntax error or failed to understand aliasing on a profound level. If so, please... explain... slowly...
  4. There is probably a better way to do this. If so, please... explain... slowly...
  5. I may be prematurely enthusiastic and PHP doesn't do this yet. Let me down gently.

Thanks!

도움이 되었습니까?

해결책 2

So the unofficial "official" answer is:

You can do it without aliasing, insteadof or anything! But not yet...

I upgraded from 5.5.1 to 5.5.6 but it was all in vain. I will update this answer when the fix becomes available. Interesting to note is that you can call trait static functions directly. The following example works:

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

Yes, yes, you can also do that with a class but classes are soooo last season.

다른 팁

You need to make use of the keyword insteadof to resolve the conflicts in Traits.

Source

Rewriting your

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

to

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

will resolve the conflicts.

I found another way to fix temporary this:

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

And it works :)

A little hack, just add function booboo to class DoSomethingSpectacular

<?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() {}
}

This is fixed in PHP 7.3: 3v4l.org/qulMv. This means, using one or more base traits in two or more derived traits and then using the derived ones in a class is a non-issue!

Note that, unfortunately, it isn't documented in PHP 7.3 migration guide, however it exists in the changelog (identical trait methods raise errors during composition).

One way to do this, is to use abstract methods. I call this "flattening traits", or it can also be called "dependency injection for traits". This is more of a generalized solution rather than specific.

Your example can be changed to this:

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;
}

This is where I personally think traits become powerful: You can write a completely independent trait and use it anywhere you want. In other words, they can be totally standalone. This is what makes me love traits (or at least PHP traits).

Although I prefer to use traits this way, there may be use-cases for making traits dependent on each other. For example, grouping some related traits into one big trait.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top