4
class a
{
    public function f(&$ref1, &$ref2)
    {
        $ref1 = 'foo';
        $ref2 = 'bar';
    }
}

class b
{
    public function __call($methodName, $arguments)
    {
        $a = new a();
        call_user_func_array(array(
            $a, $methodName
        ), $arguments);
    }
}

$ref1 = 'X';
$ref2 = 'Y';
$b = new b();
$b->f($ref1, $ref2);
var_dump($ref1, $ref2);

This results in:

PHP Warning:  Parameter 1 to a::f() expected to be a reference, value given in /home/jon/sync_workspace/bugsync/tests/test.php on line 18
PHP Stack trace:
PHP   1. {main}() /test.php:0
PHP   2. b->f() /test.php:23
PHP   3. b->__call() /test.php:23
PHP   4. call_user_func_array() /test.php:17
string(1) "X"
string(1) "Y"

How can I accomplish the above in PHP 5.4 (manipulate ref1 and ref2 by use of reference)?

In PHP 5.3 I used the & syntax at $b->f(&$ref1, &$ref2); (even though it's deprecated), but in PHP5.4 this throws fatal error.

hakre
  • 193,403
  • 52
  • 435
  • 836
Jon Skarpeteig
  • 4,118
  • 7
  • 34
  • 53
  • Your `$arguments` should contain references, which it does not. Loop trough it, create another array that contains references and pass it to the `call_user_func_array`. [Here's a link to the comment at php.net showing how to do it](http://www.php.net/manual/en/function.call-user-func-array.php#91503). – N.B. Jul 03 '12 at 13:05
  • The PHP reference mentions "Note that the parameters for call_user_func() are not passed by reference." I can't think of another current solution than to call that method directly, without the `__call()`/`call_user_func_array()` detour. I don't know your use-case, but maybe it would help simply to extend `A` with `B`, having function `f()` available on both classes… – feeela Jul 03 '12 at 13:05
  • The problem is only partly at call_user_func_array(), it's also at __call() - "None of the arguments of these magic methods can be passed by reference." - you'd need to solve both to get your desired result. – symcbean Jul 03 '12 at 13:38
  • You describe pretty well how PHP works. For deprecated code, fix it. You then normally do not have a problem with the next PHP version. If your code relies on a "feature" that is not available later on, do not upgrade your PHP version and start to isolate the software to bunker it for eternity. – hakre Jul 03 '12 at 16:05

2 Answers2

6

I managed to find a solution, although it's a hack.

You can still store references in an array, and pass the array as an argument, which will survive through __call()

class b
{
    public function __call($methodName, $arguments)
    {
        $a = new a();
        call_user_func_array(array(
            $a, $methodName
        ), reset($arguments));
    }
}
$ref1 = 'X';
$ref2 = 'Y';
$b = new b();
$b->f(array(&$ref1, &$ref2));

PHP manual states: Function definitions alone are enough to correctly pass the argument by reference. (http://php.net/manual/en/language.references.pass.php) which clearly isn't the case for __call() referenced functions!

Jon Skarpeteig
  • 4,118
  • 7
  • 34
  • 53
  • I just found this solution myself! However, the only thing you need your `reset()` call for is to return the first element in the array - The internal array pointer is meaningless. You can easily get the first element with `array_shift()`, or access it directly via `$arguments[0]`. – nickb Jul 03 '12 at 13:32
0

I take it back, this is in fact possible, using an array of references. Here is the complete code that I used:

class b
{
    public function __call($methodName, $arguments)
    {
        $a = new a();
        call_user_func_array(array(
            $a, $methodName
        ), $arguments[0]);
    }
}
$ref1 = 'X';
$ref2 = 'Y';
$b = new b();
$b->f( array( &$ref1, &$ref2));
var_dump($ref1, $ref2);

This outputs:

string(3) "foo"
string(3) "bar"

Just as expected, with no warnings or notices.

nickb
  • 59,313
  • 13
  • 108
  • 143
  • I've seen `call_user_func_array` not allowing that, too. I would not rely on this for stable code. Instead pass objects. – hakre Jul 03 '12 at 16:46
  • Passing objects is the best approach. I'm curious as to what test cases make it fail, as it seems that [this works](http://3v4l.org/JcMn0) on a bunch of PHP versions between 5.0.0 to 5.4.4. – nickb Jul 03 '12 at 16:50
  • I tried to reproduce just because I know that has happened but was not able to. I've been runnning over it some time ago, but related to PHP 5.3 in conjunction with wordpress. If you don't want any wired bugs, I can not suggest to rely on the behaviour. Just warning, because that time it was a akward to find out. – hakre Jul 03 '12 at 16:53