如何覆盖 trait 函数并从被覆盖的函数中调用它?

463

场景:

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

这段代码无法工作,我找不到一种方法来调用一个像继承来的特质函数。我尝试调用self::calc($v)static::calc($v)parent::calc($v)A::calc($v) 和以下方式:

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

class MyClass {
    use A {
        calc as traitcalc;
    }

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

什么都没起作用。

有没有办法让它工作,或者我必须完全覆盖复杂得多的特质函数? :)

6个回答

805

你的最后一个几乎就做到了:

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

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

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

这个特质不是一个类。你不能直接访问它的成员。它基本上只是自动复制和粘贴...


38
澄清一下,一旦您的类定义了相同的方法,它会自动覆盖特质的方法。当该方法为空时,特质会像 @ircmaxell 所提到的那样填充该方法。 - Yehosef
3
如果您能够更详细地说明“not work as expected”的含义,这将会很有帮助。现有的描述并没有提供足够的信息来理解出现了什么样的错误行为,并且无法确保这是否是您的暂时性错误。或许有相关的Stack Overflow问题可以解决您提到的问题吗?(如果可以的话)谢谢。 - Kamafeather
6
问题在于 trait 中的其他方法不再被包含在内。 - malhal
3
仅供参考:如果你的特征函数是静态的,你可以通过调用 A::calc(1) 来访问该函数。 - velop
7
正如菲利普所提到的(我想),您如何在保留同一特质所有其他方法正常工作情况下,仅针对一个方法进行操作?最好不要明确引用每个方法。 - Gannet
显示剩余5条评论

25

如果类直接实现该方法,则不会使用 traits 版本。也许你想到的是:

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

由于子类没有直接实现该方法,因此它们将首先使用特质的方法,否则使用父类的方法。

如果您愿意,特质可以使用父类中的方法(假设您知道该方法会在那里),例如:

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)

您也可以提供覆盖方式,但仍可通过以下方式访问特质方法:

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

您可以在http://sandbox.onlinephpfunctions.com/code/e53f6e8f9834aea5e038aec4766ac7e1c19cc2b5 查看其工作。


13

如果感兴趣,还有一种另类的方法 - 使用一个额外的中间类来使用普通的OOO方式。这样可以简化使用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);
    }
}

6
这种方法会消除您使用“特质”所获得的任何优势,就像在多个类中组合多个特质(例如,在一个类中组合特质A、B,在另一个类中组合特质B、C、D,在另一个类中组合特质A、C等)一样。 - Ionuț Staicu
3
不,使用这种方法仍然具有拥有特征的优势。您可以在IntClass中使用此特征,但如果需要,您也可以在许多其他类中使用它。如果特性仅用于IntClass,则特性将是无用的。在这种情况下,最好直接将calc()方法放置在该类中。 - marcini
这对我完全行不通。ScreenablePerson::save() 存在,Candidate 使用 Validating 特性并扩展 ScreenablePerson,而且所有三个类都有 save() - Theodore R. Smith
我同意这很有趣。它能够工作,因为对于扩展类来说,trait 方法实际上存在于父类中。个人认为如果你发现自己处于这种情况下,它可能很方便,但我不建议通过设计来实现它。 - AeonOfTime

5

使用另一个特性:

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

5
另一个变化:在trait中定义两个函数,一个是受保护的函数来执行实际任务,另一个是公共函数,用于调用受保护的函数。这样做可以避免类必须要使用'use'语句来覆盖该函数,因为它们仍然可以在内部调用受保护的函数。
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

0

这基本上是一个技巧,但如果特性是你的代码的一部分而不是一个包,你可以有一个函数来检查另一个函数是否存在并返回它而不是当前的函数。

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

希望这可以帮助到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接