1

I have a class that looks like this. I will also paste it below for reference:

<?php

trait T1
{
    abstract protected function _doStuff();
}

trait T2
{
    protected function _doStuff()
    {
        echo "Doing stuff in trait\n";
    }
}

class C
{
    use T1 {
        _doStuff as _traitDoStuff;
    }

    use T2 {
        _doStuff as _traitDoStuff;
    }

    protected function _doStuff()
    {
        echo "Doing stuff in class\n";

        $this->_traitDoStuff();
    }
}

Here's what's happening here:

  1. T1::_doStuff() is used, and aliased as _traitDoStuff(). As per PHP docs, this does not rename the method, but only adds an alias. So at this point, both _doStuff() and _traitDoStuff() exist as abstract protected methods.
  2. T2::_doStuff() is used, and aliased as _traitDoStuff(). As per PHP docs, due to precedence, both are overridden by methods of T2. So at this point, T1::_doStuff() no longer exists. Even if it would, it would be implemented by T2::_doStuff().
  3. C implements _doStuff(), which calls _traitDoStuff(). At this point, regardless of which implementation of _doStuff() is used, it is obvious that this method is implemented, so the contract defined by T1::_doStuff() is satisfied, or doesn't exist.

And yet, when I run this, it gives the following error:

Fatal error: Class C contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (C::_doStuff)

As can be seen from 3v4l, this manifests everywhere from PHP 5.4 to 7.2, which kinda hints that this is not an early trait bug. Can somebody please explain this to me?

Update

Apparently, I just forgot to specify the method that I am aliasing, i.e. T1::_doStuff as _traitDoStuff.

10
  • 1
    Shouldn't T1 be implemented as an interface, not a trait? Commented May 16, 2018 at 15:29
  • Not sure what that is supposed to mean, @bassxzero. Commented May 16, 2018 at 15:30
  • php.net/manual/en/language.oop5.interfaces.php Commented May 16, 2018 at 15:31
  • I know what interfaces are. I don't know what your sentence means. I have a trait. I don't understand what it means for a trait to be implemented as an interface. Commented May 16, 2018 at 15:32
  • Shouldn't class C implement the T1 interface. Put your abstract function do_stuff inside an interface called T1, then make class C implement the T1 interface. Commented May 16, 2018 at 15:34

2 Answers 2

3

The lack of class-scope resolution operators (T1:: and T2::) were masking a deeper issue. Consider these simpler cases:

trait A {                                                                        
    abstract public function foo();                                              
}                                                                                

class B {                                                                        
    use A; // works                                                              
    public function foo() {}                                                     
}                                                                                

class C {                                                                        
    use A { A::foo as traitFoo; } // works, provided this:                       
    public function traitFoo() {} // <-- is present                              
    public function foo() {}                                                     
}                                                                                

class D {                                                                        
    use A { A::foo as traitFoo; } // does not work by itself                     
    public function foo() {}                                                     
}                                                                                

What actually happens: aliasing an abstract method introduces another abstract method in your class. The PHP.net engine error message is hugely misleading:

Fatal error: Class D contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (D::foo)

But the HHVM engine is far more informative:

Fatal error: Uncaught Error: Class D contains abstract method (traitFoo) and must therefore be declared abstract or implement the remaining methods

The Horizontal Reuse (aka Trait) RFC does not explicitly discuss this case, so this is arguably a bug. Feel free to report it at bugs.php.net.


So why did adding the class resolution operator fix it?

When you added the class-scope resolution operators, which contained:

use T2 { T2::_doStuff as _traitDoStuff; }

you were satisfying the "phantom" abstract protected function _traitDoStuff introduced by:

use T1 { T1::_doStuff as _traitDoStuff; }

If you had removed the aliasing, like use T2; or use T2 { T2::_doStuff as _anotherMethod; } you would see the same crash.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the explanation!
2

Something like that maybe ?

<?php

trait T1
{
    abstract protected function _doStuff();
}

trait T2
{
    protected function _doStuff()
    {
        echo "Doing stuff in trait\n";
    }
}

class C
{
    use T1 {
        T1::_doStuff as _traitDoStuff;
    }

    use T2 {
        T2::_doStuff as _traitDoStuff;
    }

    protected function _doStuff()
    {
        echo "Doing stuff in class\n";

        $this->_traitDoStuff();
    }
}

7 Comments

I add T1:: and T2::. I try this code on phptester.net and I didn't get any error. Hope that help you
But I'm not sure why your first code not work. I was thinking both syntax was correct so if someone know why, please make a post to explain :)
Yea. I would also like to know, what does _doStuff as _traitDoStuff actually do.
I will explain... First code was not worked because in T1 trait you have declared function _doStuff() as abstract and that abstract method you are using in class C without implemented. That is the reason behind it.
@Kozame I've taken a stab at an explanation.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.