skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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("/rn|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();
        }
    }
    

  2. This is not call-time pass by reference. The parameter to the function is a PHP array, which is passed by value.

    However, individual elements of PHP arrays can be references, and the new by-value array will copy over the reference, not the value it points to. As the PHP manual puts it:

    In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.

    To see more clearly, let’s look at an example with no functions involved:

    $myVar = 42;
    // Make an array with a value and a reference
    $array = [
        'value' => $myVar,
        'reference' => &$myVar
    ];
    // Make a copy of the array
    $newArray = $array;
    
    // Assigning to the value in the new array works as normal
    // - i.e. it doesn't affect the original array or variable
    echo "Assign to 'value':n";
    $newArray['value'] = 101;
    var_dump($myVar, $array, $newArray);
    
    echo "nn";
    
    // Assigning to the reference in the new array follows the reference
    // - i.e. it changes the value shared between both arrays and the variable
    echo "Assign to 'reference':n";
    $newArray['reference'] = 101;
    var_dump($myVar, $array, $newArray);
    

    Output:

    Assign to 'value':
    int(42)
    array(2) {
      ["value"]=>
      int(42)
      ["reference"]=>
      &int(42)
    }
    array(2) {
      ["value"]=>
      int(101)
      ["reference"]=>
      &int(42)
    }
    
    
    Assign to 'reference':
    int(101)
    array(2) {
      ["value"]=>
      int(42)
      ["reference"]=>
      &int(101)
    }
    array(2) {
      ["value"]=>
      int(101)
      ["reference"]=>
      &int(101)
    }
    

    Additionally, this way I can get a C# like parameter out functionality.

    You do not need any hacks to achieve an out parameter. Pass-by-reference is still fully supported, it’s just the responsibility of the function to say that it uses it, not the code that calls the function.

    function doSomething(&$output) {
        $output = 'I did it!';
    }
    
    $output = null;
    doSomething($output);
    echo $output;
    

    C# out parameters also need to be part of the function definition:

    To use an out parameter, both the method definition and the calling method must explicitly use the out keyword.

    The only difference is that PHP only has an equivalent for ref, not out and in:

    [The out keyword] is like the ref keyword, except that ref requires that the variable be initialized before it is passed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search