Comment remplacer la fonction trait et l'appeler à partir de la fonction substituée?

370

Scénario:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A;

    function calc($v) {
        $v++;
        return A::calc($v);
    }
}

print (new MyClass())->calc(2); // should print 4

Ce code ne fonctionne pas et je ne peux pas trouver un moyen d'appeler une fonction trait comme si elle avait été héritée. J'ai essayé d' appeler self::calc($v), static::calc($v), parent::calc($v), A::calc($v)et les éléments suivants:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A {
        calc as traitcalc;
    }

    function calc($v) {
        $v++;
        return traitcalc($v);
    }
}

Rien ne fonctionne.

Existe-t-il un moyen de le faire fonctionner ou dois-je remplacer complètement la fonction de trait qui est beaucoup plus complexe que cela :)

Shu
la source

Réponses:

641

Votre dernier était presque là:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A {
        calc as protected traitcalc;
    }

    function calc($v) {
        $v++;
        return $this->traitcalc($v);
    }
}

Le trait n'est pas une classe. Vous ne pouvez pas accéder directement à ses membres. Il s'agit simplement de copier-coller automatisé ...

ircmaxell
la source
20
juste pour clarifier - une fois que votre classe définit la même méthode, elle remplace automatiquement celle du trait. Le trait remplit la méthode comme le mentionne @ircmaxell lorsqu'elle est vide.
Yehosef
2
@PhillipWhelan serait bien si vous pouviez ajouter plus d'informations sur ce qui "ne fonctionne pas comme prévu". Écrit comme ça, cela n'aide pas beaucoup à comprendre à quel type de mauvais comportement s'attendre et ne nous assure pas que ce n'est pas une erreur temporaire de votre part. Peut-être y a-t-il une SO question sur le problème dont vous parlez? (Finalement) Merci.
Kamafeather
1
Le problème est que toutes les autres méthodes du trait ne sont plus incluses.
malhal
2
Juste pour référence: si votre fonction trait est statique, vous pouvez accéder à la fonction en appelantA::calc(1)
velop
4
Comme Phillip l'a mentionné (je pense), comment feriez-vous cela pour une méthode d'un trait tout en incluant toutes les autres méthodes du même trait que la normale? De préférence sans référencer explicitement chaque méthode.
Fou de Bassan
14

Si la classe implémente directement la méthode, elle n'utilisera pas la version des traits. Peut-être que vous pensez à:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    function calc($v) {
        return $v+2;
    }
}

class MyChildClass extends MyClass{
}

class MyTraitChildClass extends MyClass{
    use A;
}

print (new MyChildClass())->calc(2); // will print 4

print (new MyTraitChildClass())->calc(2); // will print 3

Parce que les classes enfants n'implémentent pas directement la méthode, elles utiliseront d'abord celle du trait s'il y a sinon celle de la classe parent.

Si vous le souhaitez, le trait peut utiliser la méthode dans la classe parente (en supposant que vous savez que la méthode serait là), par exemple

trait A {
    function calc($v) {
        return parent::calc($v*3);
    }
}
// .... other code from above
print (new MyTraitChildClass())->calc(2); // will print 8 (2*3 + 2)

Vous pouvez également prévoir des moyens de remplacer, mais toujours accéder à la méthode de trait comme suit:

trait A {
    function trait_calc($v) {
        return $v*3;
    }
}

class MyClass {
    function calc($v) {
        return $v+2;
    }
}


class MyTraitChildClass extends MyClass{
    use A {
      A::trait_calc as calc;
    }
}


class MySecondTraitChildClass extends MyClass{
    use A {
      A::trait_calc as calc;
    }

    public function calc($v) {
      return $this->trait_calc($v)+.5;
    }
}


print (new MyTraitChildClass())->calc(2); // will print 6
echo "\n";
print (new MySecondTraitChildClass())->calc(2); // will print 6.5

Vous pouvez le voir fonctionner à http://sandbox.onlinephpfunctions.com/code/e53f6e8f9834aea5e038aec4766ac7e1c19cc2b5

Yehosef
la source
8

Une approche alternative si vous êtes intéressé - avec une classe intermédiaire supplémentaire pour utiliser la voie OOO normale. Cela simplifie l'utilisation avec parent :: methodname

trait A {
    function calc($v) {
        return $v+1;
    }
}

// an intermediate class that just uses the trait
class IntClass {
    use A;
}

// an extended class from IntClass
class MyClass extends IntClass {
    function calc($v) {
        $v++;
        return parent::calc($v);
    }
}
Kartik V
la source
6
Cette approche réduira tout avantage dont vous disposez en utilisant le par trait. Comme combiner plusieurs traits dans plusieurs classes (par exemple, trait A, B dans une classe, trait B, C, D dans une autre classe, trait A, C dans une autre classe, etc.)
Ionuț Staicu
3
Non, en utilisant cette approche, vous avez toujours l'avantage d'avoir un trait. Vous pouvez utiliser cette caractéristique dans IntClass, mais vous pouvez également l'utiliser dans de nombreuses autres classes si vous le souhaitez. Le trait sera inutile s'il n'était utilisé que dans IntClass. Dans ce cas, il serait préférable de placer la méthode calc () directement dans cette classe.
marcini
Cela ne fonctionnerait pas du tout pour moi. ScreenablePerson::save()existe,Candidate utilise Validatingtrait et étend ScreenablePerson, et les trois classes ont save().
Theodore R. Smith,
1

Utiliser un autre trait:

trait ATrait {
    function calc($v) {
        return $v+1;
    }
}

class A {
    use ATrait;
}

trait BTrait {
    function calc($v) {
        $v++;
        return parent::calc($v);
    }
}

class B extends A {
    use BTrait;
}

print (new B())->calc(2); // should print 4
tarkhov
la source