Search code examples
phpphp-8

PHP 8.x get variable reference info (call time pass reference)


Call time pass by reference was removed in PHP 5.4. But I have a special case where it seems to be still available in PHP 8 (tried it here: www.w3schools.com):

$myVar = "original";
testFunc([$myVar]);
echo "Variable value: $myVar<br>"; // Output is:  "Variable value: original"
testFunc([&$myVar]);
echo "Variable value: $myVar<br>"; // Output is:  "Variable value: changed"

testFunc([&$undefinedVar]);
echo "Variable value: $undefinedVar<br>"; // Output is:  "Variable value: changed"
testFunc([$undefinedVar_2]);
echo "Variable value: $undefinedVar_2<br>"; // Output is:  "Variable value: "

function testFunc( array $arr ) : void
{
  if ( !is_array($arr)
    || count($arr) == 0 )
    return;
  $arr[0] = 'changed';
}

Additionally, this way I can get a C# like parameter out functionality. Maybe I am misunderstanding something.

Question:
How could I identify within "testFunc" if $arr[0] was passed by reference or normally?

Alternative question (for people who are searching this topic):
Check if variable was passed by reference.


Solution

  • The code by @Foobar pointed me to the right direction. I am using the output of var_dump to analyze it and create a data structure out of it:

    class ReferenceInfo
    {
        public string $Type;
        public bool $IsReference;
        /** int, float, double, bool are always initialized with default value  */
        public bool $IsInitialized;
        /** @var ?ReferenceInfo[] */
        public ?array $SubItems;
    
        public static function FromVariable( mixed $arr ) : ?ReferenceInfo
        {
            /** @var ReferenceInfo $rootRefInfo */
            $rootRefInfo = NULL;
    
            $varInfoStr = self::varDumpToString($arr);
            $varInfoStrArray = preg_split("/\r\n|\n|\r/", $varInfoStr);
            $refInfoObjectStack = [];
            $curKey = NULL;
            foreach ( $varInfoStrArray as $line ) {
                $lineTrimmed = trim($line);
                $lineTrimmedLen = strlen($lineTrimmed);
                if ( $lineTrimmedLen == 0 )
                    continue;
    
                if ( $lineTrimmed == '}' ) {
                    array_pop($refInfoObjectStack);
                    $curKey = NULL;
                    continue;
                }
    
                if ( $lineTrimmed[0] == '[' ) {
                    // Found array key
                    $bracketEndPos = strpos($lineTrimmed, ']');
                    if ( $bracketEndPos === false )
                        return NULL;
                    
                    $keyName = self::convertToRealType(substr($lineTrimmed, 1, $bracketEndPos - 1));
                    $curKey = $keyName;
                    continue;
                }
    
                $parenPos = strpos($lineTrimmed, '(');
                if ( $parenPos === false ) {
                    // Must be a NULL type
                    $parenPos = $lineTrimmedLen;
                }
    
                $type = substr($lineTrimmed, 0, $parenPos);
                $isInitialized = true;
                if ( $type == 'uninitialized' ) {
                    $parenEndPos = strpos($lineTrimmed, ')', $parenPos);
                    if ( $parenEndPos === false )
                        return NULL;
    
                    $type = substr($lineTrimmed, $parenPos + 1, $parenEndPos - $parenPos - 1);
                    $isInitialized = false;
                }
    
                $refInfoObj = new ReferenceInfo();
                $refInfoObj->IsReference = str_starts_with($type, '&');
                $refInfoObj->IsInitialized = $isInitialized;
                $refInfoObj->Type = substr($type, $refInfoObj->IsReference ? 1 : 0);
                if ( $rootRefInfo == NULL ) {
                    $rootRefInfo = $refInfoObj;
                } else {
                    $refInfoObjectStack[count($refInfoObjectStack) - 1]->SubItems[$curKey] = $refInfoObj;
                }
    
                if ( $refInfoObj->Type == 'array'
                    || $refInfoObj->Type == 'object' ) {
                    $refInfoObj->SubItems = [];
                    $refInfoObjectStack[] = $refInfoObj;
                }
            }
    
            return $rootRefInfo;
        }
    
        private static function convertToRealType( string $keyName ) : float|int|string
        {
            if ( $keyName[0] == '"' ) {
                $keyName = substr($keyName, 1, strlen($keyName) - 2);
            } else if ( is_numeric($keyName) ) {
                if ( str_contains($keyName, '.') )
                    $keyName = doubleval($keyName);
                else
                    $keyName = intval($keyName);
            }
            
            return $keyName;
        }
    
        private static function varDumpToString( mixed $var ) : string
        {
            ob_start();
            var_dump($var);
            return ob_get_clean();
        }
    }