Note:
Late static bindings' resolution will stop at a fully resolved static call with no fallback. On the other hand, static calls using keywords like parent:: or self:: will forward the calling information.
Example #4 Forwarding and non-forwarding calls
https://www.php.net/manual/en/language.oop5.late-static-bindings.php
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo(); // what? - Why is resolved to C if B's father is A?
self::foo(); // what? - Why is it not resolved in B?
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
?>
Output:
A
C
C
I don't understand the use of parent and self in this example, could you please explain?
During each call to a static method, the PHP runtime knows two pieces of information:
static::refers toself::refers to, and also what the magic constant__CLASS__will resolve toLet's look at a simpler example first:
Calling
A::test();will outputDefined in A; called in A-selfandstaticrefer to the same class.Calling
B::test();will outputDefined in A; called in B- although there is no method calledtest()defined in classB, PHP still knows that you called it while referring toB.The "forwarding" comes in when you use
self::more than once - PHP keeps track of the original calling context:Outputs:
What happens here is this:
B, and looks for a methodforward_test()forward_testis in classA, and outputs our first line of debugforward_testcallsself::test(), and two things happentestis looked up in class A, because that's where our definition offorward_testisB, becauseselfcalls "forward" this informationA::test()is called, but with a calling context ofB, resulting in our second line of debugInternally, you can imagine the compiler replacing each method call with a
call_methodfunction that needs the target class name, the method name, and the calling context.For
self::test(), it can immediately replaceselfwith the current class, which isA, and outputs something like:Only when it runs, is
$currentCallingContextdefined, and forwarded.An explicit call to
A::test()explicitly defines both the target class and the calling context:Conversely, a "Late Static Binding" call to
static::test()defines the target class based on the calling context:The same thing is happening with the
parentcalls in the example in the question:C::test()B, but the calling context isCparent::foo()resolves to the definition inA, but the calling context is forwarded, so is stillCA::foo()with a calling context ofCstatic::who()which looks at the calling context to decide which method to run ("late static binding"), so it runsC::who()C, and the magic constant__CLASS__is the string'C'An expanded example that shows some more information, and more variations:
Outputs: