Reputation:
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:
What I have tried:
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:
Thanks!
Upvotes: 29
Views: 14316
Reputation: 4288
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.
Upvotes: 0
Reputation: 4288
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).
Upvotes: 0
Reputation:
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.
Upvotes: 5
Reputation: 1
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() {}
}
Upvotes: 0
Reputation: 1212
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 :)
Upvotes: 2
Reputation: 68476
You need to make use of the keyword insteadof
to resolve the conflicts in Traits.
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.
Upvotes: 23