ChatGPT解决这个技术问题 Extra ChatGPT

How to override trait function and call it from the overridden function?

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 :)


M
Maciej Pyszyński

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


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.
@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.
The problem is all the other methods in the trait are no longer included.
Just for reference: If your trait function would be static you could access the function by calling A::calc(1)
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.
Y
Yehosef

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


K
Kartik V

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

This approach will shave any advantage you have by using traits. 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)
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.
This totally wouldn't work for me. ScreenablePerson::save() exists, Candidate uses Validating trait and extends ScreenablePerson, and all three classes have save().
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.
t
tarkhov

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

G
Gannet

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