As noted on Reddit's LOL PHP sub, PHP 7 may use either the extended class or the base class when referring to self
, in contrast to PHP 5 which always refers to the extended class.
<?php
class Foo {
const A = "FooA";
const B = self::A . self::C;
const C = "FooC";
}
class Bar extends Foo {
const A = "BarA";
const C = "BarC";
}
var_dump(Bar::B);
Try it online
string(8) "BarABarC"
string(8) "FooABarC"
The behaviour of PHP 7 is particularly worrisome as there does not seem to be any simple rule to know when self
refers to the base class or the extended class. What are rules for determining to which class self
will refer in PHP 7?
self::
should always refer to the class it is used in (note that PHP 5's behavior is wrong too.)
This was a bug, fixed in 7.1.4, that applied to resolution of self::
and parent::
only within class constants.
Basically in:
const B = self::A . self::C;
self::C
is still unknown at this point and resolution is deferred. At the point of eventual resolution the proper scope was unfortunately lost.
The problem is also more subtle than simply base vs extended as you can end up with a value from a different extending class. E.g.:
https://3v4l.org/cY1I0
class A {
const selfN = self::N;
const N = 'A';
}
class B extends A {
const N = 'B';
}
class C extends A {
const N = 'C';
}
var_dump(B::selfN); // A
var_dump(C::selfN); // A
In PHP 7.0.0 through 7.1.3 this outputs:
string(1) "B"
string(1) "B"
Though if you swap it for:
https://3v4l.org/RltQj
var_dump(C::selfN); // A
var_dump(B::selfN); // A
You'll get:
string(1) "C"
string(1) "C"
To avoid this in affected versions use the class name rather than self::
in class constant definitions, e.g. const selfN = A::N
The expected behavior outlined in this example is undefined. Foo
's reference to self::C
is dynamically referencing a constant before it's been defined. If you were to execute Foo::B
I would expect it to throw a warning.
For more context, see this "not a bug" bug report:
LSB works by storing the class named in the last "non-forwarding call" so the key here is to understand what a forwarding call is: A "forwarding call" is a static one that is introduced by self::, parent::, static::.
LSB and its forwarded call tracking are separate mechanisms from class resolution done through ::. When you use self::method() or static::method() the LSB class is unchanged, however the two will (potentially) resolve to and call different method()s. In other words, the internal state is the same but the actual code being executed next differs
However the interpreter chooses to handle it, coding something this way in a real-world application is a probably bad idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With