461

Scenario:

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

This code doesn't work, and I cannot find a way to call a trait function like it was inherited. I tried calling self::calc($v), static::calc($v), parent::calc($v), A::calc($v) and the following:

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

class MyClass {
    use A {
        calc as traitcalc;
    }

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

Nothing works.

Is there a way to make it work or must I override completely the trait function which is much more complex than this :)

Kyle Barron
  • 2,452
  • 22
  • 17
Shu
  • 4,869
  • 2
  • 15
  • 9

6 Answers6

800

Your last one was almost there:

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

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

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

The trait is not a class. You can't access its members directly. It's basically just automated copy and paste...

Maciej Pyszyński
  • 9,266
  • 3
  • 25
  • 28
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 38
    just to clarify - once your class defines the same method, it automatically overrides the trait's. The trait fills in the method as @ircmaxell mentions when it's empty. – Yehosef Jun 08 '14 at 12:43
  • 1
    For as yet unknown reasons this totally screwed up a trait I applied this with. The other methods the trait is pulling in may not work as expected when you do this. – Phillip Whelan Sep 11 '14 at 23:14
  • 3
    @PhillipWhelan would be nice if you could add more information on what does "not work as expected". Written like that it doesn't help much in understanding what kind of wrong behaviour to expect, and does not assure us that this is not a temporary mistake of you. Maybe there is some SO question about the issue you are talking about? (Eventually) Thanks. – Kamafeather Mar 29 '15 at 14:40
  • 6
    The problem is all the other methods in the trait are no longer included. – malhal Mar 31 '15 at 13:15
  • 2
    I see now that what PhillipWhelan said is fine if he would have used commas, like so: "The other methods the trait is pulling in may not work, as expected, when you do this." In other words, he is saying that the other trait methods aren't working at all. malhal clarifies this point for us in their comment. – OCDev Dec 25 '15 at 15:06
  • 2
    @malhal Under what circumstances does this occur? I [can't reproduce](https://3v4l.org/EWFnU). – J.D. Jul 25 '16 at 15:59
  • 3
    Just for reference: If your trait function would be static you could access the function by calling `A::calc(1)` – velop Mar 24 '17 at 22:26
  • 7
    As Phillip mentioned (I think), how would you do this for one method of a trait while still including all other methods of the same trait as normal? Preferrably without explicitly referencing each method. – Gannet May 01 '17 at 03:41
  • This also works nested trait-on-trait. Trait A can have a method renamed by a trait B that overrides that method, so that trait B can call the original method of trait A. It's a handy way to add a wrapper to a trait using another trait, with the ability to fallback to methods in the original trait. – Jason May 29 '21 at 10:46
  • This also works with static methods in traits (ex: https://3v4l.org/aLAe1) – Syscall Jan 10 '23 at 22:44
25

If the class implements the method directly, it will not use the traits version. Perhaps what you are thinking of is:

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

Because the child classes do not implement the method directly, they will first use that of the trait if there otherwise use that of the parent class.

If you want, the trait can use method in the parent class (assuming you know the method would be there) e.g.

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)

You can also provide for ways to override, but still access the trait method as follows:

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

You can see it work at http://sandbox.onlinephpfunctions.com/code/e53f6e8f9834aea5e038aec4766ac7e1c19cc2b5

Yehosef
  • 17,987
  • 7
  • 35
  • 56
13

An alternative approach if interested - with an extra intermediate class to use the normal OOO way. This simplifies the usage with 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
  • 467
  • 4
  • 4
  • 6
    This approach will shave any advantage you have by using `trait`s. Like combining multiple traits in multiple classes (e.g. trait A, B in a class, trait B, C, D in another class, trait A, C in another class and so on) – Ionuț Staicu Aug 21 '15 at 05:50
  • 3
    No, using this approach you still have the advantages of having a trait. You can use this trait in IntClass, but you can also use it in many another classes if you want to. Trait will be useless, if it was used only in IntClass. In that case, it would be better to place calc() method directly in that class. – marcini Jul 13 '16 at 08:11
  • This totally wouldn't work for me. `ScreenablePerson::save()` exists, `Candidate` uses `Validating` trait and extends `ScreenablePerson`, and all three classes have `save()`. – Theodore R. Smith Dec 11 '18 at 23:39
  • I do agree that it's interesting. It works, because for the extending class, the trait methods actually exist in the parent class. Personally I think it can be convenient if you find yourself in that situation, but I would not recommend doing it by design. – AeonOfTime Jan 29 '21 at 09:08
5

Using another 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
  • 324
  • 4
  • 8
5

Another variation: Define two functions in the trait, a protected one that performs the actual task, and a public one which in turn calls the protected one.

This just saves classes from having to mess with the 'use' statement if they want to override the function, since they can still call the protected function internally.

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

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

class MyClass {
    use A;

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

class MyOtherClass {
    use A;
}


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

print (new MyOtherClass())->calc(2); // will print 3
Gannet
  • 1,315
  • 13
  • 18
0

This is basically a hark, but if the trait is part of your code and not a package, you can have a function that checks if another function exists and return it instead of the current one.

trait A {
    function calc($v) {
        if(method_exists($this, 'calcClass'))
        {
          $this->calcClass($v)
        }
        return $v+1;
    }
}

class MyClass {
    use A;

    function calcClass($v) {
        return $v+2;;
    }
}

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

I hope this helps.

Razor Mureithi
  • 351
  • 1
  • 4
  • 15