13

I've just changed all my sql queries to prepared statements using mysqli. To speed this process up I created a function (called performQuery) which replaces mysql_query. It takes the query, the bindings (like "sdss") and the variables to pass in, this then does all the perpared statement stuff. This meant changing all my old code was easy. My function returns a mysqli_result object using mysqli get_result().

This meant I could change my old code from:

$query = "SELECT x FROM y WHERE z = $var";
$result = mysql_query($query);
while ($row = mysql_fetch_assoc($result)){
    echo $row['x'];
}

to

$query = "SELECT x FROM y WHERE z = ?";
$result = performQuery($query,"s",$var);
while ($row = mysql_fetch_assoc($result)){
    echo $row['x'];
}

This works fine on localhost, but my web hosting server does not have mysqlnd available, therefore get_result() does not work. Installing mysqlnd is not an option.

What is the best way to go from here? Can I create a function which replaces get_result(), and how?

Timm
  • 12,553
  • 4
  • 31
  • 43

3 Answers3

30

Here is a neater solution based on the same principle as lx answer:

function get_result( $Statement ) {
    $RESULT = array();
    $Statement->store_result();
    for ( $i = 0; $i < $Statement->num_rows; $i++ ) {
        $Metadata = $Statement->result_metadata();
        $PARAMS = array();
        while ( $Field = $Metadata->fetch_field() ) {
            $PARAMS[] = &$RESULT[ $i ][ $Field->name ];
        }
        call_user_func_array( array( $Statement, 'bind_result' ), $PARAMS );
        $Statement->fetch();
    }
    return $RESULT;
}

With mysqlnd you would normally do:

$Statement = $Database->prepare( 'SELECT x FROM y WHERE z = ?' );
$Statement->bind_param( 's', $z );
$Statement->execute();
$Result = $Statement->get_result();
while ( $DATA = $Result->fetch_array() ) {
    // Do stuff with the data
}

And without mysqlnd:

$Statement = $Database->prepare( 'SELECT x FROM y WHERE z = ?' );
$Statement->bind_param( 's', $z );
$Statement->execute();
$RESULT = get_result( $Statement );
while ( $DATA = array_shift( $RESULT ) ) {
    // Do stuff with the data
}

So the usage and syntax are almost identical. The main difference is that the replacement function returns a result array, rather than a result object.

Sophivorus
  • 3,008
  • 3
  • 33
  • 43
7

I encountered the same problem and solved it using the code provided in the answer of What's wrong with mysqli::get_result?

My function looks like this now (error handling stripped out for clarity):

  function db_bind_array($stmt, &$row)
  {
    $md = $stmt->result_metadata();
    $params = array();
    while($field = $md->fetch_field()) {
        $params[] = &$row[$field->name];
    }
    return call_user_func_array(array($stmt, 'bind_result'), $params);
  }

  function db_query($db, $query, $types, $params)
  {
    $ret = FALSE;
    $stmt = $db->prepare($query);
    call_user_func_array(array($stmt,'bind_param'),
                         array_merge(array($types), $params));
    $stmt->execute();

    $result = array();
    if (db_bind_array($stmt, $result) !== FALSE) {
      $ret = array($stmt, $result);
    }

    $stmt->close();
    return $ret;
  }

Usage like this:

  $userId = $_GET['uid'];
  $sql = 'SELECT name, mail FROM users WHERE user_id = ?';
  if (($qryRes = db_query($db, $sql, 'd', array(&$userId))) !== FALSE) {
    $stmt = $qryRes[0];
    $row  = $qryRes[1];

    while ($stmt->fetch()) {
      echo '<p>Name: '.$row['name'].'<br>'
             .'Mail: '.$row['mail'].'</p>';
    }
    $stmt->close();
  }
Community
  • 1
  • 1
lx.
  • 2,317
  • 1
  • 22
  • 32
  • Where and what is $var used for? And.. where is $bindRet used for?? Why not use $stmt->bind_param()? – Melroy van den Berg May 30 '14 at 15:26
  • @danger89: `$var` is the parameter that's inserted into the SELECT-statement (notice the `?`). As for `$bindRet`: I wrote "(most of the error handling stripped out for clarity)". In my "real code" I check it for `!== FALSE` and act accordingly. Not using `bind_param` directly had something to do with keeping the references intact. I'm not really sure on that one, since I haven't used it in a long time. Sorry there. – lx. Jun 03 '14 at 06:33
  • @danger89 I updated my post to make the example-usage clearer. – lx. Jun 03 '14 at 06:57
  • Is there a function that acts as an exact replacement? For instance instead of `$result = $stmt->get_result();` you have `$result = getResult($stmt);` and in the end the `$result` is the same. – Luke Apr 22 '15 at 04:34
  • I don't think so Luke, but check my answer for a very close approximation. – Sophivorus May 30 '15 at 21:20
  • Could you explain what the parameter `$type` is in the `db_query` function? – Kellen Stuart Apr 07 '16 at 04:34
  • Look at the answer below by Sophivorus. Much easier to use. – Kellen Stuart Apr 07 '16 at 05:00
0

I found the anonymous advice posted as a note at the API documentation page for mysqli_stmt::get_result very useful (I couldn't think of a better way than the eval trick), as we very often want to fetch_array() on our result. However, because I wanted to cater for a generic database object, I found it a problem that the code assumed numeric array was fine for all callsites, and I needed to cater for all callers using assoc arrays exclusively. I came up with this:

class IImysqli_result {
        public $stmt, $ncols;
}   

class DBObject {

    function iimysqli_get_result($stmt) {
      $metadata = $stmt->result_metadata();
      $ret = new IImysqli_result;
      if (!$ret || !$metadata) return NULL; //the latter because this gets called whether we are adding/updating as well as returning
      $ret->ncols = $metadata->field_count;
      $ret->stmt = $stmt;
      $metadata->free_result();
      return $ret;
   }

   //this mimics mysqli_fetch_array by returning a new row each time until exhausted
    function iimysqli_result_fetch_array(&$result) {
      $stmt = $result->stmt;
      $stmt->store_result();
      $resultkeys = array();
      $thisName = "";
      for ( $i = 0; $i < $stmt->num_rows; $i++ ) {
            $metadata = $stmt->result_metadata();
            while ( $field = $metadata->fetch_field() ) {
                $thisName = $field->name;
                $resultkeys[] = $thisName;
            }
      }

      $ret = array();
      $code = "return mysqli_stmt_bind_result(\$result->stmt ";
      for ($i=0; $i<$result->ncols; $i++) {
          $ret[$i] = NULL;
          $theValue = $resultkeys[$i];
          $code .= ", \$ret['$theValue']";
      }

      $code .= ");";
      if (!eval($code)) { 
        return NULL; 
      }

      // This should advance the "$stmt" cursor.
      if (!mysqli_stmt_fetch($result->stmt)) { 
        return NULL; 
      }

      // Return the array we built.
      return $ret;
    }
}
tonywoode
  • 166
  • 1
  • 6