Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Under what conditions does PHP 7's self refer to the base class?

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

PHP 5

string(8) "BarABarC"

PHP 7

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?

like image 893
dotancohen Avatar asked Jul 10 '17 19:07

dotancohen


2 Answers

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

like image 89
user3942918 Avatar answered Sep 16 '22 18:09

user3942918


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.

like image 42
Matt S Avatar answered Sep 19 '22 18:09

Matt S