views:

100

answers:

1

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.

+3  A: 

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 "2, 4, 6"

?>

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

meagar
Call me crazy, but did you mean for your `test` method to be `pow2` or vice versa?
Anthony Forloney
@anthony The former, thank you.
meagar
@meager, Not a problem, it's late over here was just curious if I had started to hallucinate.
Anthony Forloney
@meager: yeah, that's about the best I was able to come up with. I was hoping (still am, really) that my imagination was failing me.
outis