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:
class A {
public static function test() {
echo 'Defined in ', self::class, '; called in ', static::class, "\n";
}
}
class B extends A {
}
Calling A::test();
will output Defined in A; called in A
- self
and static
refer to the same class.
Calling B::test();
will output Defined in A; called in B
- although there is no method called test()
defined in class B
, PHP still knows that you called it while referring to B
.
The "forwarding" comes in when you use self::
more than once - PHP keeps track of the original calling context:
class A {
public static function test() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
}
public static function forward_test() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
self::test();
}
}
class B extends A {
public static function test() {
echo 'this method is not called from forward_test()';
}
}
B::forward_test();
Outputs:
forward_test defined in A; called in B
test defined in A; called in B
What happens here is this:
B
, and looks for a method forward_test()
forward_test
is in class A
, and outputs our first line of debugforward_test
calls self::test()
, and two things happen
test
is looked up in class A, because that's where our definition of forward_test
isB
, because self
calls "forward" this informationA::test()
is called, but with a calling context of B
, resulting in our second line of debugInternally, you can imagine the compiler replacing each method call with a call_method
function that needs the target class name, the method name, and the calling context.
For self::test()
, it can immediately replace self
with the current class, which is A
, and outputs something like:
call_method(targetClass: 'A', methodName: 'test', callingContext: $currentCallingContext)
Only when it runs, is $currentCallingContext
defined, and forwarded.
An explicit call to A::test()
explicitly defines both the target class and the calling context:
call_method(targetClass: 'A', methodName: 'test', callingContext: 'A')
Conversely, a "Late Static Binding" call to static::test()
defines the target class based on the calling context:
call_method(targetClass: $currentCallingContext, methodName: 'test', callingContext: $currentCallingContext)
The same thing is happening with the parent
calls in the example in the question:
C::test()
B
, but the calling context is C
parent::foo()
resolves to the definition in A
, but the calling context is forwarded, so is still C
A::foo()
with a calling context of C
static::who()
which looks at the calling context to decide which method to run ("late static binding"), so it runs C::who()
C
, and the magic constant __CLASS__
is the string 'C'
An expanded example that shows some more information, and more variations:
class A {
public static function foo() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
static::who();
}
public static function who() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
echo __CLASS__."\n";
}
}
class B extends A {
public static function foo() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
static::who();
}
public static function test() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
echo "A::foo():\n";
A::foo();
echo "B::foo():\n";
B::foo();
echo "C::foo():\n";
C::foo();
echo "parent::foo():\n";
parent::foo();
echo "self::foo():\n";
self::foo();
}
public static function who() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __FUNCTION__, ' defined in ', self::class, '; called in ', static::class, "\n";
echo __CLASS__."\n";
}
}
C::test();
Outputs:
test defined in B; called in C
A::foo():
foo defined in A; called in A
who defined in A; called in A
A
B::foo():
foo defined in B; called in B
who defined in B; called in B
B
C::foo():
foo defined in B; called in C
who defined in C; called in C
C
parent::foo():
foo defined in A; called in C
who defined in C; called in C
C
self::foo():
foo defined in B; called in C
who defined in C; called in C
C