web-dev-qa-db-fra.com

Pourquoi PHP Trait ne peut pas implémenter d'interfaces?

Je me demande pourquoi PHP Trait (PHP 5.4) ne peut pas implémenter d'interfaces.

La mise à jour de la réponse de user1460043 => ... ne peut pas nécessiter de classe qui l'utilise pour implémenter une interface spécifique

Je comprends que cela pourrait être évident, car les gens pourraient penser que si un Class A utilise un Trait T qui implémente un interface I, que le Class A devrait implémenter le interface I indirectement (et ce n'est pas vrai car Class A pourrait renommer les méthodes de trait).

Dans mon cas, mon trait appelle des méthodes à partir de l'interface que la classe utilisant le trait implémente.

Le trait est en fait une implémentation de certaines méthodes de l'interface. Donc, je veux "concevoir" dans le code que chaque classe qui veut utiliser mon trait doit implémenter l'interface. Cela permettrait au Trait d'utiliser des méthodes de classe définies par l'interface et d'être sûr qu'elles existent dans la classe.

77
Leto

La version vraiment courte est plus simple car vous ne pouvez pas. Ce n'est pas ainsi que les traits fonctionnent.

Lorsque vous écrivez use SomeTrait; in PHP vous dites (effectivement) au compilateur de copier et coller le code du Trait dans la classe où il est utilisé.

Parce que le use SomeTrait; est dans la classe, il ne peut pas ajouter implements SomeInterface à la classe, car cela doit être en dehors de la classe.

"Pourquoi les types Traits ne sont-ils pas en PHP?"

Parce qu'ils ne peuvent pas être instanciés. Les traits sont vraiment juste un construction de langage (dire au compilateur de copier et coller le code de trait dans cette classe) par opposition à un objet ou un type qui peut être référencé par votre code.

Donc, je veux "concevoir" dans le code que chaque classe qui veut utiliser mon trait doit implémenter l'interface.

Cela peut être appliqué en utilisant une classe abstraite pour use le trait, puis en étendant les classes à partir de celui-ci.

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

Cependant - si vous devez faire en sorte que toute classe qui utilise un trait ait une méthode particulière, je pense que vous utilisez peut-être des traits où vous auriez dû être des classes abstraites en premier lieu.

Ou que vous avez votre logique à l'envers. Vous êtes censé exiger que les classes qui implémentent des interfaces aient certaines fonctions, pas que si elles ont certaines fonctions, elles doivent se déclarer comme implémentant une interface.

Modifier

En fait, vous pouvez définir des fonctions abstraites dans Traits pour forcer une classe à implémenter la méthode. par exemple.

trait LoggerTrait {

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

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

Cependant, cela ne vous permet toujours pas d'implémenter l'interface dans le trait, et sent toujours comme une mauvaise conception, car les interfaces sont bien meilleures que les traits pour définir un contrat qu'une classe doit remplir.

88
Danack

Il y a RFC: Traits avec interfaces suggère d'ajouter ce qui suit à la langue:

trait SearchItem implements SearchItemInterface
{
    ...
}

Les méthodes requises par l'interface peuvent être soit implémentées par le trait, soit déclarées abstraites, auquel cas la classe qui utilise le trait l'implémente.

Cette fonctionnalité n'est actuellement pas prise en charge par le langage, mais elle est à l'étude (l'état actuel du RFC est: En cours de discussion ).

20
Ilija

[...] pour "concevoir" dans le code que chaque classe qui veut utiliser mon trait doit implémenter l'interface. Cela permettrait au Trait d'utiliser des méthodes de classe définies par l'interface et d'être sûr qu'elles existent dans la classe.

Cela semble très raisonnable et je ne dirais pas qu'il doit y avoir un problème avec votre conception. Des traits ont été suggérés avec cette idée en tête, voir le deuxième point ici:

  • Un trait fournit un ensemble de méthodes qui implémentent le comportement.
  • Un trait nécessite un ensemble de méthodes qui servent de paramètres au comportement fourni.
  • [...]

Schärli et al, Traits: Composable Units of Behavior, ECOOP’2003, LNCS 2743, p. 248-274, Springer Verlag, 2003, page 2

Il serait donc peut-être plus approprié de dire que vous voulez qu'un trait nécessite une interface, pas de "l'implémenter".

Je ne vois pas pourquoi il devrait être impossible d'avoir cette fonctionnalité "nécessite (ses classes de consommateurs à implémenter) une fonctionnalité d'interface" en PHP, mais actuellement elle semble faire défaut.

Comme @Danack le note dans son réponse , vous pouvez utiliser des fonctions abstraites dans le trait pour les "exiger" des classes qui utilisent le trait. Malheureusement, vous ne pouvez pas le faire avec fonctions privées .

7
user1460043

Je suis d'accord avec la réponse de @Danack, mais je vais la compléter un peu.

La version vraiment courte est plus simple car vous ne pouvez pas. Ce n'est pas ainsi que les traits fonctionnent.

Je ne peux penser qu'à quelques cas dans lesquels ce que vous demandez est nécessaire et est plus évident comme un problème de conception que comme une défaillance de la langue. Imaginez simplement qu'il existe une interface comme celle-ci:

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

Un trait a été créé qui implémente l'une des fonctions définies dans le interface mais dans le processus utilise d'autres fonctions également définies par le interface, sujettes à l'erreur que si la classe qui utilise la fonctionnalité n'implémente pas interface tout ne parvient pas à tirer le déclencheur

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

class Warrior
{
    use Triggerable;
}

Une solution simple consiste simplement à forcer la classe qui utilise le trait à implémenter ces fonctions également:

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

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

Ainsi, le trait ne dépend pas complètement de interface, mais une proposition d'implémenter l'une de ses fonctions, puisque lors de l'utilisation du trait la classe exigera la mise en œuvre des méthodes abstraites.

Conception finale

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

Veuillez excuser mon anglais

0
NekoOs