Why PHP Trait can't implement interfaces?

43,714

Solution 1

The really short version is simpler because you can't. That's not how Traits work.

When you write use SomeTrait; in PHP you are (effectively) telling the compiler to copy and paste the code from the Trait into the class where it's being used.

Because the use SomeTrait; is inside the class, it can't add implements SomeInterface to the class, because that has to be outside the class.

"why aren't Traits types in PHP? "

Because they can't be instantiated. Traits are really just a language construct (telling the compiler to copy and paste the trait code into this class) as opposed to an object or type that can be referenced by your code.

So, i want to "design" in the code that every class that want to use my trait have to implement the interface.

That can be enforced using an abstract class to use the trait and then extending classes from it.

interface SomeInterface{
    public function someInterfaceFunction();
}

trait SomeTrait {
    function sayHello(){
        echo "Hello my secret is ".static::$secret;
    }
}

abstract class AbstractClass implements SomeInterface{
    use SomeTrait;
}

class TestClass extends AbstractClass {
    static public  $secret = 12345;

    //function someInterfaceFunction(){
        //Trying to instantiate this class without this function uncommented will throw an error
        //Fatal error: Class TestClass contains 1 abstract method and must therefore be 
        //declared abstract or implement the remaining methods (SomeInterface::doSomething)
    //}
}

$test = new TestClass();

$test->sayHello();

However - if you do need to enforce that any class that uses a Trait has a particular method, I think you may be using traits where you should have been abstract classes in the first place.

Or that you have your logic the wrong way round. You're meant to require classes that implement interfaces have certain functions, not that if they have certain functions that they must declare themselves as implementing an interface.

Edit

Actually you can define abstract functions inside Traits to force a class to implement the method. e.g.

trait LoggerTrait {

    public function debug($message, array $context = array()) {
        $this->log('debug', $message, $context);
    }

    abstract public function log($level, $message, array $context = array());
}

However this still doesn't allow you to implement the interface in the trait, and still smells like a bad design, as interfaces are much better than traits at defining a contract that a class needs to fulfill.

Solution 2

There's a RFC: Traits with interfaces suggests following to be added to the language:

trait SearchItem implements SearchItemInterface
{
    ...
}

Methods required by the interface can either be implemented by the trait, or declared as abstract, in which case it is expected that class that uses the trait implements it.

This feature is currently not supported by the language, but it is under consideration (current status of the RFC is: Under Discussion).

Solution 3

[...] to "design" in the code that every class that want to use my trait have to implement the interface. That would allow the Trait to use class methods defined by the interface and be sure they are existing in the class.

This sounds very reasonable and I would not say that there has to be anything wrong with your design. Traits have been suggested with this idea in mind, see the second point here:

  • A trait provides a set of methods that implement behaviour.
  • A trait requires a set of methods that serve as parameters for the provided behaviour.
  • [...]

Schärli et al, Traits: Composable Units of Behaviour, ECOOP’2003, LNCS 2743, pp. 248–274, Springer Verlag, 2003, Page 2

So it would be maybe more appropriate to say that you want a trait to require an interface, not to "implement" it.

I do not see a reason why it should be impossible to have this "trait requires (its consumer classes to implement) an interface" feature in PHP, but currently it seems to be missing.

As @Danack notes in his answer, you can use abstract functions in the trait to "require" them from classes that use the trait. Unfortunately you can not do this with private functions.

Solution 4

I agree with the response of @Danack, however I will complement it a little.

The really short version is simpler because you can't. That's not how Traits work.

I can only think of few cases in which what you request is necessary and is more evident as a design problem than as a language failure. Just imagine that there is an interface like this:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

A trait has been created that implements one of the functions defined in the interface but in the process uses other functions also defined by the interface, prone to the error that if the class that uses the feature does not implement the interface everything fails to pull the trigger

trait Triggerable
{
    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

class Warrior
{
    use Triggerable;
}

An easy solution is simply to force the class that uses the trait to implement those functions as well:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

So the trait isn't completely dependent on the interface, but a proposal to implement one of its functions, since when using the trait the class will demand the implementation of the abstract methods.

Final design

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}


class Warrior implements Weaponize
{
    use Triggerable;

    public function hasAmmunition()
    {
        // TODO: Implement hasAmmunition() method.
    }

    public function fire()
    {
        // TODO: Implement fire() method.
    }

    public function recharge()
    {
        // TODO: Implement recharge() method.
    }
}

Please excuse my English

Share:
43,714
Leto
Author by

Leto

Updated on August 02, 2020

Comments

  • Leto
    Leto almost 4 years

    I'm wondering why PHP Trait (PHP 5.4) cannot implement interfaces.

    Update from user1460043's answer => ...cannot require class which uses it to implement a specific interface

    I understand that it could be obvious, because people could think that if a Class A is using a Trait T which is implementing an interface I, than the Class A should be implementing the interface I undirectly (and this is not true because Class A could rename trait methods).

    In my case, my trait is calling methods from the interface that the class using the trait implements.

    The trait is in fact an implementation of some methods of the interface. So, i want to "design" in the code that every class that want to use my trait have to implement the interface. That would allow the Trait to use class methods defined by the interface and be sure they are existing in the class.

  • scragar
    scragar almost 10 years
    How would you suggest laying this out then, I have a Human class, this class is abstracted into subclasses based on Job, but many of these jobs share features which are best implemented with shared code(for example both a secretary and programmer need the type method). Can you think of how this could be implemented without traits?
  • Danack
    Danack almost 10 years
    @scragar you should ask that over at programmers.stackexchange.com but the short version is that I'd composite 'Human' with multiple 'Jobs' to be an 'WorkingHuman' class.
  • lazycommit
    lazycommit over 8 years
    One more. If interface define some awareness contract and that contract is common for most of the implementations. But those implementations have their own type three. Something like Command with ContainerAwareInterface. But Comand has their own specific areas of usage. So I need to repeat myself every time I need Container Awareness but if I use Trait I can not define its own contract for specific Interface. Maybe core developers should consider Go-Type interfaces (e.g Structural Typing)?
  • Summer-Sky
    Summer-Sky about 8 years
    it's really strange, since actually my colleagues and i only ever using traits when we want to share code that is required in several classes implementing an interface but coming from different ancestors. also there is no reasonable explanation why compiler can alter the code inside the class but not the interfaces implemented by this class. ... it's simply a "missing" feature ... "because you can't" explains this the best
  • Vincent Pazeller
    Vincent Pazeller over 7 years
    I believe that PHP core developers should study a bit of Scala, where traits are considered full-fledged types... I find it sad that PHP gradually wants to improve its typing system but does not take into account existing well working implementations
  • Kamafeather
    Kamafeather almost 5 years
    I suppose that, if confirmed, then people will want more and more features from normal classes to be implemented into traits. Until there'll be no difference among them and we'll have some kind of Frankenstein-trait that doesn't split concerns & responsibilities properly. As the best answer underlines, traits are to be seen as copy-past convenience; shouldn't cross boundaries of classes too much. We want a class to implement an interface, whether the implementation comes from direct code or from the using a trait; allow to implement interfaces into traits might be confusing and misleading
  • The Mighty Chris
    The Mighty Chris over 4 years
    One great application of traits is providing an easy-to-paste default implementation of an interface. If you wanted to ensure that a trait fulfilled an interface it would be nice to have the compilers help
  • The Mighty Chris
    The Mighty Chris over 4 years
    This proposal does not make it so a class using a trait would automatically implement those traits interfaces (that would not work since you can rename/replace trait methods). The slippery slope argument that passing this will somehow pass more aggressive future RFCs shouldn't hold water
  • Dávid Bíró
    Dávid Bíró over 3 years
    I think, only traits should exist, nor interfaces, nor abstract classes. And traits should be types in the language.
  • toraman
    toraman about 3 years
    If you replace the interface with a trait full of abstract methods, then you can use the trait in the trait (I heard you liked traits). But I don't really know much about interfaces are much better than traits at defining a contract that a class needs to fulfill
  • toraman
    toraman about 3 years
    I looked it up. You give up on enforcing the signature that way. Probably some other features as well. I really hope the traits thing evolves into multiple inheritance in the future. It seems like it needs a refactoring though.