Bug Wrong resolution of "Late Static Binding" after self::method()

php.internals

Marc

6 years ago
Hi, I have reported a bug in Mai 2019 but did not get any response here https://bugs.php.net/bug.php?id=77985. The problem is that "static::class" as well as "get_called_class()" used within a method resolves to a different class if this method was called with "self::method". Interestingly it works as expected if the method got called with "ClassName::method". I send it here because fixing this could result in a hard to find BC break and it could be a good idea to handle this in 8.0. https://3v4l.org/NSdB8 class A {     static function call() {         self::method();         $self = self::class;         $self::method();     }     static function method() { echo static::class; } } class B extends A {} B::call(); // displays "BA" instead of "AA" In case this is "Not a Bug" please explain to me why the result of "self::method()" is different then "$self = self::class; $self::method();". Thanks, Marc

Rowan Collins

6 years ago
On 8 March 2020 09:47:37 GMT+00:00, Marc <marc@mabe.berlin> wrote:
>https://3v4l.org/NSdB8 > >class A { >     static function call() { >         self::method(); > >         $self = self::class; >         $self::method(); >     } >     static function method() { echo static::class; } >} > >class B extends A {} > >B::call(); // displays "BA" instead of "AA"
Someone else may be able to confirm if this is a bug or working as designed, but what seems to be happening is that the value of "static" is not reset when calling a method using self:: This doesn't just apply to getting the class name, but to actual static calls; for instance: # https://3v4l.org/p2g8E class A { static function call() { self::method1(); } static function method1() { static::method2(); } static function method2() { echo 'Base definition'; } } class B extends A { static function method2() { echo 'Override'; } } B::call(); If the call to self::method1() reset the called class, this would run A::method2() and echo 'Base definition'; instead, the called class is remembered, and it calls B::method2() and echoes 'Override'. If you explicitly call A::method1(), as you are effectively doing in your self::class example, the "called class" information is reset, and the call goes through to A::method2(). Regards, Hi Marc,
-- Rowan Tommins [IMSoP]

Matthew Brown

6 years ago
This is expected behaviour given my understanding of how late static binding works: If there is a chain of “self::” calls that ultimately ends in “static::someMethod”, then PHP behaves as if every preceding call was a call to “static::”, not “self::”. In your second call you’re explicitly overriding that resolution by changing using (self::class)::someMethod, which PHP always treats as A::someMethod, thus producing the expected output.

Marc

6 years ago
Hi, On 08.03.20 17:54, Matthew Brown wrote:
> This is expected behaviour given my understanding of how late static binding works: > > If there is a chain of “self::” calls that ultimately ends in “static::someMethod”, then PHP behaves as if every preceding call was a call to “static::”, not “self::”. In your second call you’re explicitly overriding that resolution by changing using (self::class)::someMethod, which PHP always treats as A::someMethod, thus producing the expected output.
Thanks for explaining. I couldn't find any information for this behavior in the documentation - Is that documented anywhere? Does it make sense? -> I have read "self::" all time as a shortcut for "MyClass::" until I noticed this is not the case and I expect most PHP devs would explain it this way. Is there a reason why self:: doesn't reset the internal "static" reference? Sorry for all these questions. Sometimes the world could be so simple until it turns out it isn't for no reason. Thanks, Marc

Nikita Popov

6 years ago
On Mon, Mar 9, 2020 at 6:38 AM Marc <marc@mabe.berlin> wrote:
> Hi, > > > On 08.03.20 17:54, Matthew Brown wrote: > > This is expected behaviour given my understanding of how late static > binding works: > > > > If there is a chain of “self::” calls that ultimately ends in > “static::someMethod”, then PHP behaves as if every preceding call was a > call to “static::”, not “self::”. In your second call you’re explicitly > overriding that resolution by changing using (self::class)::someMethod, > which PHP always treats as A::someMethod, thus producing the expected > output. > > Thanks for explaining. > > I couldn't find any information for this behavior in the documentation - > Is that documented anywhere? > > Does it make sense? -> I have read "self::" all time as a shortcut for > "MyClass::" until I noticed this is not the case and I expect most PHP > devs would explain it this way. > > Is there a reason why self:: doesn't reset the internal "static" reference? > > > Sorry for all these questions. > > Sometimes the world could be so simple until it turns out it isn't for > no reason. > > > Thanks, > > Marc >
See https://wiki.php.net/rfc/lsb_parentself_forwarding, which is the RFC that added this behavior. Nikita

Rowan Collins

6 years ago
On Mon, 9 Mar 2020 at 05:38, Marc <marc@mabe.berlin> wrote:
> > Does it make sense? -> I have read "self::" all time as a shortcut for > "MyClass::" until I noticed this is not the case and I expect most PHP > devs would explain it this way. > > Is there a reason why self:: doesn't reset the internal "static" reference? >
A reasonably intuitive parallel that occurred to me is that $this is also maintained through self:: calls (as long as they're not to a method declared static). So if you replace the static:: call in my previous example with a $this-> call, you get the same result: # https://3v4l.org/deRcD class A { function call() { self::method1(); } function method1() { $this->method2(); } function method2() { echo 'Base definition'; } } class B extends A { function method2() { echo 'Override'; } } (new B)->call(); # echoes 'Override' Regards,
-- Rowan Tommins [IMSoP]

Marc

6 years ago
Thank you all for explaining. This helps a lot! Marc On 09.03.20 10:27, Rowan Tommins wrote: