During each call to a static method, the PHP runtime knows two pieces of information:
- The calling context - normally, the class which was referred to when you called the method; this is what
static::
refers to
- The class where the actual definition of the method is located; this is what
self::
refers to, and also what the magic constant __CLASS__
will resolve to
Let'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:
- PHP sets the calling context to class
B
, and looks for a method forward_test()
- The definition of
forward_test
is in class A
, and outputs our first line of debug
- Now
forward_test
calls self::test()
, and two things happen
- the definition of
test
is looked up in class A, because that's where our definition of forward_test
is
- but the calling context is kept as class
B
, because self
calls "forward" this information
- So
A::test()
is called, but with a calling context of B
, resulting in our second line of debug
Internally, 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:
- We call
C::test()
- The definition is in
B
, but the calling context is C
- The call to
parent::foo()
resolves to the definition in A
, but the calling context is forwarded, so is still C
- So we are running
A::foo()
with a calling context of C
- We then call
static::who()
which looks at the calling context to decide which method to run ("late static binding"), so it runs C::who()
- We are now in a method defined in class
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