97 votes

Pourquoi le Trait PHP ne peut pas implémenter les interfaces ?

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

Mise à jour de la réponse de l'utilisateur1460043 => ...ne peut pas exiger que la classe qui l'utilise implémente une interface spécifique

Je comprends que cela puisse être évident, parce que les gens pourraient penser que si un Class A utilise un Trait T qui met en œuvre une interface I que le Class A devrait mettre en œuvre le interface I de manière non indirecte (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 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 les méthodes de classe définies par l'interface et d'être sûr qu'elles existent dans la classe.

120voto

Danack Points 8764

La version vraiment courte est plus simple parce que vous ne pouvez pas. Ce n'est pas comme ça que les traits fonctionnent.

Quand vous écrivez use SomeTrait; en PHP, vous demandez (en fait) au compilateur de copier et coller le code du Trait dans la classe où il est utilisé.

Parce que le use SomeTrait; se trouve à l'intérieur de la classe, il ne peut pas ajouter l'option implements SomeInterface à la classe, parce que ça 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 du langage (indiquant au compilateur de copier et coller le code du 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 doivent implémenter l'interface.

Cela peut être mis en œuvre en utilisant une classe abstraite pour use le trait et ensuite étendre 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 avez besoin d'imposer que toute classe qui utilise un Trait possède une méthode particulière, je pense que vous utilisez des Traits là où vous auriez dû utiliser des classes abstraites en premier lieu.

Ou que votre logique est à l'envers. Vous êtes censé exiger que les classes qui implémentent des interfaces aient certaines fonctions, et non 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 à l'intérieur des 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 cela sent toujours la mauvaise conception, car les interfaces sont bien meilleures que les traits pour définir un contrat qu'une classe doit remplir.

31voto

Ilija Points 518

Il y a un RFC : Traits avec interfaces propose d'ajouter les éléments suivants à 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 il est attendu que 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 (statut actuel du RFC) : En cours de discussion ).

11voto

user1460043 Points 755

[...] de "concevoir" dans le code que chaque classe qui veut utiliser mon trait doivent implémenter l'interface. Cela permettrait au trait d'utiliser les méthodes de la 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 y a forcément 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 mettent en œuvre le comportement.
  • Un trait nécessite un ensemble de méthodes qui servent de paramètres pour le comportement fourni.
  • [...]

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

Il serait donc peut-être plus approprié de dire que vous voulez qu'un trait de caractère exiger une interface, et non pour la "mettre en œuvre".

Je ne vois pas pourquoi il serait impossible d'avoir cette fonctionnalité "le trait nécessite (ses classes consommatrices d'implémenter) une interface" en PHP, mais actuellement elle semble manquer.

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

6voto

NekoOs Points 130

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

La version vraiment courte est plus simple parce que vous ne pouvez pas. Ce n'est pas comme ça que les traits fonctionnent.

Je ne peux penser qu'à quelques cas où 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();
}

A trait a été créé et met en œuvre l'une des fonctions définies dans le fichier interface mais utilise dans le processus d'autres fonctions également définies par la interface En effet, si la classe qui utilise la fonctionnalité n'implémente pas la fonction interface tout échoue pour appuyer sur la gâchette

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

class Warrior
{
    use Triggerable;
}

Une solution facile consiste simplement à forcer la classe qui utilise l'attribut trait pour mettre en œuvre 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 la interface mais une proposition d'implémentation de l'une de ses fonctions, puisque lors de l'utilisation de la fonction trait la classe exigera l'implémentation 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X