1

Today I saw a new type of code execution in PHP that was slightly different from the previous versions. The following code is executed as follows from version 8.1 onward. Anyone have an idea why?

<?php

class test {

    /**
     * A test static variable in class
     * @var int $var
     */
    private static int $var;

    /**
     * A method to set value in private variable with name $var
     * @param int $var
     */
    public static function setVar(int $var): void
    {
        self::$var = $var;
    }

    public static function getVar()
    {
        $vars = get_class_vars(__CLASS__);
        echo '<pre>' . print_r($vars, 1) . '</pre>';
    }

}

test::setVar(2);
test::getVar();
test::setVar(3);
test::getVar();

Run result in PHP 8.0.11

Array
(
    [var] => 2
)

Array
(
    [var] => 3
)

Run result in PHP 8.1.6

Array
(
    [var] => 
)

Array
(
    [var] => 
)
Morteza
  • 41
  • 4
  • i m thinking you have a different code executed . Check [this](https://stackoverflow.com/a/5011889/14629458) answer to confirm aboute old PHP behavior – Yasser CHENIK Aug 05 '22 at 21:04
  • The documentation says that it returns an associative array containing the default values, not the current values. And I don't see any notes saying that this changed between versions. – Barmar Aug 05 '22 at 21:05
  • If you change the `get_class_vars()` to a `var_dump(self::$var)`, the execution is the same across all versions. So the difference is in the `get_class_vars()` function itself, but I don't see any documented change. – Alex Howansky Aug 05 '22 at 21:09
  • I ran your code at phpize.online. I got the first result in PHP 7.4, but the second result in 8.0 and 8.1. – Barmar Aug 05 '22 at 21:09
  • But I don't see any mention of this in the PHP 8 changes. – Barmar Aug 05 '22 at 21:12
  • Yeah me either, but it has definitely changed for 8.1. https://3v4l.org/mPmsP – Alex Howansky Aug 05 '22 at 21:13
  • 2
    There's a bug from November 2021 about this https://bugs.php.net/bug.php?id=81675 – Rob Ruchte Aug 05 '22 at 23:21
  • Looks like this commit made a lot of enhancements to the way that property changes are tracked internally, which "fixed" get_class_vars to work they way is is supposed to according to the documentation. https://github.com/php/php-src/commit/4b79dba93202ed5640dff317046ce2fdd42e1d82 – Rob Ruchte Aug 05 '22 at 23:48
  • I know there is no change log about this in PHP change logs. but the result is different and this surprised me. – Morteza Aug 06 '22 at 08:20
  • I see the bug report right now, Thanks to share that Rob Ruchte. Do you have any idea how to fix this problem for now? – Morteza Aug 06 '22 at 08:35
  • You should probably look into the reflection extension, I’m sure you can do what you want that way, although it may take a little more work. https://www.php.net/manual/en/book.reflection.php – Rob Ruchte Aug 06 '22 at 15:05

1 Answers1

1

The change is not to static properties themselves, but to the get_class_vars function. The documented purpose of that function has always been to return the default values of all properties, so returning the current value for static properties in previous versions was technically a bug.

To get the current values of static properties, you can instead use the method ReflectionClass::getStaticProperties, e.g.

class Test {
    public static $var = 0;
}

echo "Before:-\n";
echo "Default: "; var_dump( get_class_vars(Test::class) );
echo "Current: "; var_dump( (new ReflectionClass(Test::class))->getStaticProperties() );

test::$var = 42;

echo "After:-\n";
echo "Default: "; var_dump( get_class_vars(Test::class) );
echo "Current: "; var_dump( (new ReflectionClass(Test::class))->getStaticProperties() );

Running this across lots of PHP versions shows that for PHP versions back to 5.5 (and probably beyond, if the syntax of the example is tweaked) the output is (incorrectly):

Before:-
Default: array(1) {
  ["var"]=>
  int(0)
}
Current: array(1) {
  ["var"]=>
  int(0)
}
After:-
Default: array(1) {
  ["var"]=>
  int(42)
}
Current: array(1) {
  ["var"]=>
  int(42)
}

For PHP 8.1, it is (correctly):

Before:-
Default: array(1) {
  ["var"]=>
  int(0)
}
Current: array(1) {
  ["var"]=>
  int(0)
}
After:-
Default: array(1) {
  ["var"]=>
  int(0)
}
Current: array(1) {
  ["var"]=>
  int(42)
}

Alternatively, you could use get_class_vars to get the names of the variables, and then "variable variable" syntax to get their current value.

Note that get_class_vars returns both static and instance variables, so you need to first check that the static property exists before trying to fetch its value.

function get_current_static_vars(string $className): array {
    $allVarNames = array_keys(get_class_vars($className));
    $staticValues = [];
    foreach ( $allVarNames as $varName ) {
        if ( isset($className::$$varName) ) {
            $staticValues[ $varName ] = $className::$$varName;
        }
    }
    return $staticValues;
}

The above could easily be extended to emulate the old buggy get_class_vars (which showed defaults for instance properties, but current values for static properties); or to show the current value of instance properties given a particular instance.

IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • Your answer works correctly, but the reflection class slows the code down a lot, especially if it is used a lot in the project. – Morteza Aug 08 '22 at 14:41
  • @Morteza I have thought of an alternative approach, and added it to my answer. – IMSoP Aug 08 '22 at 15:42