13

Assuming it's possible, how would one pass arguments by reference to a variadic function without generating a warning in PHP? We can no longer use the '&' operator in a function call, otherwise I'd accept that (even though it would be error prone, should a coder forget it).

What inspired this is are old MySQLi wrapper classes that I unearthed (these days, I'd just use PDO). The only difference between the wrappers and the MySQLi classes is the wrappers throw exceptions rather than returning FALSE.

class DBException extends RuntimeException {}
...
class MySQLi_throwing extends mysqli {
    ...
    function prepare($query) {
        $stmt = parent::prepare($query);
        if (!$stmt) {
            throw new DBException($this->error, $this->errno);
        }
        return new MySQLi_stmt_throwing($this, $query, $stmt);
    }
}
// I don't remember why I switched from extension to composition, but
// it shouldn't matter for this question.
class MySQLi_stmt_throwing /* extends MySQLi_stmt */ {
    protected $_link, $_query, $_delegate;

    public function __construct($link, $query, $prepared) {
        //parent::__construct($link, $query);
        $this->_link = $link;
        $this->_query = $query;
        $this->_delegate = $prepared;
    }
    function bind_param($name, &$var) {
        return $this->_delegate->bind_param($name, $var);
    }
    function __call($name, $args) {
        //$rslt = call_user_func_array(array($this, 'parent::' . $name), $args);
        $rslt = call_user_func_array(array($this->_delegate, $name), $args);
        if (False === $rslt) {
            throw new DBException($this->_link->error, $this->errno);
        }
        return $rslt;
    }
}

The difficulty lies in calling methods such as bind_result on the wrapper. Constant-arity functions (e.g. bind_param) can be explicitly defined, allowing for pass-by-reference. bind_result, however, needs all arguments to be pass-by-reference. If you call bind_result on an instance of MySQLi_stmt_throwing as-is, the arguments are passed by value and the binding won't take.

try {
    $id = Null;
    $stmt = $db->prepare('SELECT id FROM tbl WHERE ...');
    $stmt->execute()
    $stmt->bind_result($id);
    // $id is still null at this point
    ...
} catch (DBException $exc) {
   ...
}

Since the above classes are no longer in use, this question is merely a matter of curiosity. Alternate approaches to the wrapper classes are not relevant. Defining a method with a bunch of arguments taking Null default values is not correct (what if you define 20 arguments, but the function is called with 21?). Answers don't even need to be written in terms of MySQL_stmt_throwing; it exists simply to provide a concrete example.

outis
  • 75,655
  • 22
  • 151
  • 221
  • Whoops... just found the question that this one dups: http://stackoverflow.com/questions/1925253/php-variable-length-argument-list-by-reference, though I prefer meager's answer below to the accepted answer for the other question. – outis Apr 10 '10 at 22:13

2 Answers2

11

As of PHP 5.6 you can pass arguments by reference to a variadic function. Here's an example from the RFC:

public function prepare($query, &...$params) {
    $stmt = $this->pdo->prepare($query);
    foreach ($params as $i => &$param) {
        $stmt->bindParam($i + 1, $param);
    }
    return $stmt;
}
mpen
  • 272,448
  • 266
  • 850
  • 1,236
5

There is no way of passing variable length argument lists by reference in PHP. It is a fundamental limitation of the language.

There is, however, a workaround with array(&$var1, &$var2...) syntax:

<?php

/** raise all our arguments to the power of 2 */
function pow2() {
        $args = &func_get_arg(0);

        for ($i = 0; $i< count($args); ++$i) {
            $args[$i] **= 2;
        }
}


$x = 1; $y = 2; $z = 3;
pow2(array(&$x, &$y, &$z)); // this is the important line

echo "$x, $y, $z"; // output "1, 4, 9"

Test could also be declared function test($args) but I wanted to illustrate that this works with the func_get_args() family of functions. It is the array(&$x) that causes the variable to be passed by reference, not the function signature.

From a comment on PHP's documentation on function arguments: http://php.net/manual/en/functions.arguments.php

user229044
  • 232,980
  • 40
  • 330
  • 338
  • Has this changed in PHP 5.6 with the introduction of variadics? – mpen Oct 24 '14 at 18:13
  • @Mark: with such questions, it's best to check the PHP manual. Then, armed with that information, you can post a new answer. – outis Oct 26 '14 at 23:22
  • @outis The docs actually don't say. [The RFC](https://wiki.php.net/rfc/variadics#by-reference_capture) recommended it, but I don't know if it made it into the official implementation or not. – mpen Oct 26 '14 at 23:50
  • @Mark: you might want to read the "Function Arguments" page more closely. At the end of the "[Variable-length argument lists ... in PHP 5.6+](http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list.new) section, it states: "Finally, you may also pass variable arguments by reference by prefixing the ... with an ampersand (&)." – outis Oct 26 '14 at 23:55