PHP MySQLi prepared statements and fetching subset of columns - php

I am using MySQLi and PHP to call a stored MySQL routine with prepared statements. It returns a result set with dozens of columns.
$stmt = $dbconnection->prepare("CALL SomebodysDbProcedure(?);");
$stmt->bind_param("s", $idvalue);
$stmt->execute();
$stmt->bind_result($col1, $col2, $col3, ...);
However, I am only interested in a subset of the output columns.
The documentation says bind_result() is required to handle the complete set of returned columns:
Note that all columns must be bound after mysqli_stmt_execute() and
prior to calling mysqli_stmt_fetch().
Do I need to add code also for those columns I'm uninterested in? If so the application will break if the MySQL stored routine result set is expanded in the future, or even columns rearranged. Is there a workaround for this?

I'm assuming that you just don't want to write out all those variables for the bind_result() function. You could use a function like below instead of the bind_result() function. Pass it your $stmt object and you'll get back an array of standard objects with the fields you want.
function getResult($stmt)
{
$valid_fields = array('title', 'date_created'); // enter field names you care about
if (is_a($stmt, 'MySQLi_STMT')) {
$result = array();
$metadata = $stmt->result_metadata();
$fields = $metadata->fetch_fields();
for (; ;)
{
$pointers = array();
$row = new \stdClass();
$pointers[] = $stmt;
foreach ($fields as $field)
{
if (in_array($field->name, $valid_fields)) {
$fieldname = $field->name;
$pointers[] = &$row->$fieldname;
}
}
call_user_func_array('mysqli_stmt_bind_result', $pointers);
if (!$stmt->fetch())
break;
$result[] = $row;
}
$metadata->free();
return $result;
}
return array();
}

The answer of Jonathan Mayhak guided me in the right direction. On PHP bind_result page, nieprzeklinaj provides a function called fetch(). It works; use it like this:
$stmt = $conn->prepare("CALL SomebodysDbProcedure(?);");
$stmt->bind_param("s", $idvalue);
$stmt->execute();
$sw = (array)(fetch($stmt));
$s = $sw[0]; // Get first row
$dateCreated = $s['date_created']; // Get field date_created
Edit: Unfortunately successive calls within the same PHP file don't seem to work with this method.

Try using fetch_fields php method:
array mysqli_fetch_fields ( mysqli_result $result )
http://php.net/manual/en/mysqli-result.fetch-fields.php

Related

Getting a multi dimensional array from a mysql database

Right now im using Mysqli to retrieve data from a Mysql Database, The code im using is difficult to understand and from my understanding has depreciated functions or itself in entire is depreciated.
If i could get some insight and maybe some updated techniques on my Qoal of retrieving data from a mysql DB.
mysqli prepared statements
What i have so far is this:
$param = 1;
$mysqli = new mysqli("127.0.0.1", "user", "password", "databaseName");
$mysqli->query('SELECT reply_id FROM replies WHERE reply_topic = ? ORDER BY reply_date ASC');
$mysqli->bind_param('i',$param)
$mysqli->execute();
$row = bind_result_array($mysqli);
while($mysqli->fetch()){
$set[$row['']] = getCopy($row);
}
$mysqli->free_result();
return $set;
function bind_result_array($stmt)
{
$meta = $stmt->result_metadata();
$result = array();
while ($field = $meta->fetch_field())
{
$result[$field->name] = NULL;
$params[] = &$result[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
return $result;
}
function getCopy($row)
{
return array_map(create_function('$a', 'return $a;'), $row);
}
You need to clean up the call on the database first. $mysqli->query has send the query before you bound parameters and so on.
Replace with the following;
$stmt = $mysqli->prepare('SELECT reply_id FROM replies WHERE reply_topic = ? ORDER BY reply_date ASC');
$stmt->bind_param('i',$param)
$stmt->execute();
The $mysqli->prepare returns a new object that you should bind values to and then execute. It avoids SQL injection!
None of this is depreciated. mysqli_ is exactly what you should use.
As all you want is an array of the results, you can use the following code to do so - no user functions required.
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
$set[] = $row['reply_id'];
}
For each row in the while, each field is saved into an array with the column name as the key. So $row['reply_id'] contains the value of the reply_id column in the database. Above I have saved this value to an array called $set, and when it loops over the next result row it saves the next value to that array too.
You can manipulate the result before or after saving it to the new array, as you see fit.
Hope this helps.

Pass form field names to bind parameter at class instance

I'm using a factory(class) to present forms from a target database table - as defined at class instance. Then on submit, create a new instance of the class which then insert a new record in to the database. $_POST key names match the table column names.
My issue is dynamically assigning bind parameters when the variables are determined at class instance. I'm getting the following, whether I use Reflections method or inline.
Warning: mysqli_stmt::bind_param() [mysqli-stmt.bind-param]: Number of elements in type definition string doesn't match number of bind variables
The following method is called in the sub class after the post array has been contructed and assigned to the class property $array.
private function addrecord($array,$tbl,$_conn){
//define field name array for query statement
foreach ($array as $key=>$value){
$keyarr[]=$key;
}
//BUILD THE QUERY STATEMENT
$query = "INSERT INTO $tbl SET ";
foreach ($keyarr as $key){
$query .= ($key."=?, "); //clone and add next element
}
$query = rtrim($query,", "); //remove EOL whitespace and comma
//done
/*
//Hard code bind parameters works as expected
if (self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME)){
$stmt=self::$_conn->prepare($query);
$stmt->bind_param("sssss",$array['user_id'],$array['user_name'],$array['user_email'],$array['user_date'],$array['user_active']);
$stmt->execute();
$insertid=$stmt->insert_id;
$stmt->close();
echo "The record was created with id ".$insertid;
}
*/
//Tried re assigning post array as reference
//same error as just passing $array
//$array = $this->refValues($array);
//Binding params using Reflections, same error
self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME);
$stmt = self::$_conn->prepare($query);
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt,$array);
$stmt->execute();
$stmt->close();
self::$_conn->close();
}
//Pass By Reference required for PHP 5.3+, dev server 5.3.17
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0){
$refar = array();
foreach($arr as $key => $value)
$refar[$key] = &$arr[$key];
return $refar;
}
return $arr;
}
Thanks in advance and much appreciated.
As you can see, mysqli is practically unusable with prepared statements.
So, I'd suggest you to either use PDO or, better, some intelligent library that can make safe query without prepared statments.
With such a library your function will be written in one line
private function addrecord($array,$tbl){
$this->conn->query("INSERT INTO ?n SET ?u", $tbl, $array);
}
please note that if $array is coming from the untrusted source, you have to filter it's content out first.
Per Common Sense, changed process to PDO. Works as expected. Should have done it sooner. Only issue remaining is UI feedback. Would like to return an insert id, however MySQL doesnt return a last insert id for PDO. And again, the class doesnt know the tables structure in advance so hard coding in not an option. Need a workaround. Any thoughts? Heres the new insert process using PDO.
try{
self::$_conn = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.'', DB_UNAME, DB_UPWORD);
$stmt = self::$_conn->prepare($query);
$arcount=count($array); //set number of bindParam loops
foreach($array as $key=>$value){
$stmtarr[]=$value; //convert assoc to numerated array
}
//re index array so increment will match up with placeholder position
$stmtarr = array_combine(range(1, count($stmtarr)), array_values($stmtarr));
for($i=1;$i<=$arcount;$i++){ //bind variable, one for each placeholder,
$stmt->bindParam($i,$stmtarr[$i]);
}
$stmt->execute();
} catch (PDOException $e){
print "Error: ".$e->getMessage()."<br>";
die();
}
Assume everything else is the same as above.

bind_result into an array PHP mysqli prepared statement

wondering how i could bind the results of a PHP prepared statement into an array and then how i could go about calling them. for example this query
$q = $DBH->prepare("SELECT * FROM users WHERE username = ?");
$q->bind_param("s", $user);
$q->execute();
and this would return the results the username, email, and id. wondering if i could bind it in an array, and then store it in a variable so i could call it throughout the page?
PHP 5.3 introduced mysqli_stmt::get_result, which returns a resultset object. You can then call mysqli_result::fetch_array() or
mysqli_result::fetch_assoc(). It's only available with the native MySQL driver, though.
Rule of thumb is that when you have more than one column in the result then you should use get_result() and fetch_array() or fetch_all(). These are the functions designed to fetch the results as arrays.
The purpose of bind_result() was to bind each column separately. Each column should be bound to one variable reference. Granted it's not very useful, but it might come in handy in rare cases. Some older versions of PHP didn't even have get_result().
If you can't use get_result() and you still want to fetch multiple columns into an array, then you need to do something to dereference the values. This means giving them a new zval container. The only way I can think of doing this is to use a loop.
$data = [];
$q->bind_result($data["category_name"], $data["id"]);
while ($q->fetch()) {
$row = [];
foreach ($data as $key => $val) {
$row[$key] = $val;
}
$array[] = $row;
}
Another solution as mentioned in the comments in PHP manual is to use array_map, which internally will do the same, but in one line using an anonymous function.
while ($q->fetch()) {
$array[] = array_map(fn($a) => $a , $data);
}
Both solutions above will have the same effect as the following:
$q = $DBH->prepare("SELECT * FROM users WHERE username = ?");
$q->bind_param("s", $user);
$q->execute();
$result = $q->get_result();
$array = $result->fetch_all(MYSQLI_ASSOC);
See Call to undefined method mysqli_stmt::get_result for an example of how to use bind_result() instead of get_result() to loop through a result set and store the values from each row in a numerically-indexed array.

Is it possible to use mysqli_fetch_object with a prepared statement

All the examples I see using mysqli_fetch_object use mysql_query(), I cannot get it to work with prepared statements. Does anyone know what is wrong with this code snippet, as fetch_object returns null.
$sql = "select 1 from dual";
printf("preparing %s\n", $sql);
$stmt = $link->prepare($sql);
printf("prepare statement %s\n", is_null($stmt) ? "is null" : "created");
$rc = $stmt->execute();
printf("num rows is %d\n", $stmt->num_rows);
$result = $stmt->result_metadata();
printf("result_metadata %s\n", is_null($result) ? "is null" : "exists");
$rc = $result->fetch_object();
printf("fetch object returns %s\n", is_null($rc) ? "NULL" : $rc);
$stmt->close();
The output is:
preparing select 1 from dual
prepare statement created
num rows is 0
result_metadata exists
fetch object returns NULL
This is the code I use to create an object from a prepared statement.
It could perhaps be used in a subclass of mysqli?
$query = "SELECT * FROM category WHERE id = ?";
$stmt = $this->_db->prepare($query);
$value = 1;
$stmt->bind_param("i", $value);
$stmt->execute();
// bind results to named array
$meta = $stmt->result_metadata();
$fields = $meta->fetch_fields();
foreach($fields as $field) {
$result[$field->name] = "";
$resultArray[$field->name] = &$result[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $resultArray);
// create object of results and array of objects
while($stmt->fetch()) {
$resultObject = new stdClass();
foreach ($resultArray as $key => $value) {
$resultObject->$key = $value;
}
$rows[] = $resultObject;
}
$stmt->close();
MySql Native Driver extension (mysqlnd), has the get_result method:
$stmt->execute();
$obj = $stmt->get_result()->fetch_object();
I don't believe the interface works like that.
Going by the documentation and examples (http://www.php.net/manual/en/mysqli.prepare.php) it seems that $stmt->execute() does not return a resultset, but a boolean indicating success / failure (http://www.php.net/manual/en/mysqli-stmt.execute.php). To actually get the result, you need to bind variables to the resultset (aftere the execute call) using $stmt->bind_result (http://www.php.net/manual/en/mysqli-stmt.bind-result.php).
After you did all that, you can do repeated calls to $stmt->fetch() () to fill the bound variables with the column values from the current row. I don't see any mention of $stmt->fetch_object() nor do I see how that interface could work with a variable binding scheme like described.
So this is the story for "normal" result fetching from mysqli prepared statments.
In your code, there is something that I suspect is an error, or at least I am not sure you intended to do this.
You line:
$result = $stmt->result_metadata();
assignes the resultset metadata, which is itself represented as a resultset, to the $result variable. According to the doc (http://www.php.net/manual/en/mysqli-stmt.result-metadata.php) you can only use a subset of the methods on these 'special' kinds of resultsets, and fetch_object() is not one of them (at least it is not explicitly listed).
Perhaps it is a bug that fetch_object() is not implemented for these metadata resultsets, perhaps you should file a bug at bugs.mysql.com about that.

MySQLI binding params using call_user_func_array

Please see below my code.
I am attempting to bind an array of paramenters to my prepared statement.
I've been looking around on the web and can see I have to use call_user_func_array but cannot get it to work. The error I get is:
"First argument is expected to be a valid callback, 'Array' was given"
I may be wrong but I'm assuming the first argument can be an an array and perhaps this error message is misleading. I think the issue is that my array is in someway at fault.
Can anyone see what I am doing wrong? Thanks.
$type = array("s", "s");
$param = array("string1","anotherstring");
$stmt = $SQLConnection->prepare("INSERT INTO mytable (comp, addl) VALUES (?,?)");
$params = array_merge($type, $param);
call_user_func_array(array(&$stmt, 'bind_param'), $params);
$SQLConnection->execute();
It must be like this:
//connect
$mysqli = new mysqli($host, $user, $password, $db_name);
//prepare
$stmt = $mysqli->prepare("SELECT * FROM the_table WHERE field1= ? AND Field2= ?");
//Binding parameters. Types: s = string, i = integer, d = double, b = blob
$params= array("ss","string_1","string_2");
//now we need to add references
$tmp = array();
foreach($params as $key => $value) $tmp[$key] = &$params[$key];
// now us the new array
call_user_func_array(array($stmt, 'bind_param'), $tmp);
$stmt->execute();
/* Fetch result to array */
$res = $stmt->get_result();
while($row = $res->fetch_array(MYSQLI_ASSOC)) {
$a_data[]=$row;
}
print_r($a_data);
$stmt->close();
Since PHP 5.6, you don't have to mess around with call_user_func_array() anymore.
Instead of:
$stmt->bind_param($param_types, $my_params_array);
you can just use the splat operator, like this:
$stmt->bind_param($param_types, ...$my_params_array); // exact code
I wouldn't know why you have to use call_user_func_array, but that's another story.
The only thing that could be wrong in my eyes is that you are using a reference to the object. Assuming you're using PHP 5.*, that is not necessary:
call_user_func_array(array($stmt, 'bind_param'), $params);
If you get an error, you should try this:
call_user_func_array(array($stmt, 'bind_param'), refValues($params));
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
Wasn't able to answer this on my own question because it got marked as dupe: here. But I think my final solution, which uses the answers in this question, works in my use case, might be helpful for someone.
My goals was to take a posted set of ID's and use them in a NOT IN MYSQL statement. Assuming array of 5 ID's posted.
Count the number posted ID's to build the ? placeholders for NOT IN statement. Using $params_count = substr(str_repeat(',?', count($array_of_ids)), 1); gives the result: (?,?,?,?,?) to be used in SQL statement.
Make function that takes ID's and type i or s etc. For me, they were all i so my function is simpler. return array that looks like this $params= array("iiiii",1,2,3,4,5) where the first value is the set of i's and the subsequent values are the ID's depending on total ID's passed into function.
function build_bind_params($values, $bind_type) {
$s = substr(str_repeat($bind_type, count($values)), 0);
$bind_array = array();
$bind_array[] = $s;
foreach($values as $value) {
$bind_array[] = $value;
}
return $bind_array;
}
$params = build_bind_params($array_of_ids, "i");
Then use foreach ($params as $key => $value) $tmp[$key] = &$params[$key]; to get the newly created $params formatted properly for binding.
Then use call_user_func_array(array($stmt , 'bind_param') , $tmp); to properly bind the array.
Then execute the $stmt
Most of the above are not solutions without integrating the types along with the values before adding them to call_user_func_array(). This solution worked for me:
/* create a database connection */
$db = new mysqli($host, $user, $password, $db_name);
/* setup the sql, values, and types */
$sql="SELECT * FROM languages
WHERE language_code = ?
AND charset = ?
ORDER BY native_name";
$values = array($langCode, $charset);
$types = "ss";
/* pass those variables to the execute() function defined below */
if ($rows = execute($sql, $values, $types))
{
return $rows[0];
}
function execute($sql, $values='', $types='')
{
/* prepare the sql before binding values and types */
$stmt = $db->prepare($sql);
/*combine the values and types into $inputArray */
$inputArray[] = &$types;
$j = count($values);
for($i=0;$i<$j;$i++){
$inputArray[] = &$values[$i];
}
/* add the combined values and types to call_user_func_array() for binding */
call_user_func_array(array($stmt, 'bind_param'), $inputArray);
$result = $stmt->execute();
return $result;
}
Here's a reference to the full description this example is based on:
http://big.info/2015/08/php-use-call_user_func_array-for-variable-number-of-parameters-arrays-in-prepared-statements.html
Why would you want to call call_user_func_array(array($statement, 'bind_param'), $bind_arguments)? Because $bind_arguments is an array. You get to have one function that binds a statement to its queried parameters, no matter how many parameters you'd have otherwise.
Example of good code...
<?php
# link
$dblink = new mysqli('HOSTNAME','USERNAME','PASSWORD','DATABASENAME');
# example data
$statement = $dblink->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
$recordvalues = ['John', 'H.', 'Smith', 25];
$sqlbindstring = "sssi"; # String, String, String, Integer example
# make the references
$bind_arguments = [];
$bind_arguments[] = $sqlbindstring;
foreach ($recordvalues as $recordkey => $recordvalue)
{
$bind_arguments[] = & $recordvalues[$recordkey]; # bind to array ref, not to the temporary $recordvalue
}
# query the db
call_user_func_array(array($statement, 'bind_param'), $bind_arguments); # bind arguments
$statement->execute(); # run statement
$result = $statement->get_result(); # get results
# get the results
if($result) {
while ($row = $result->fetch_assoc()) {
print("\n\nMy row is...");
print_r($row);
}
}
?>
Example of bad code...
<?php
# Same setup as above..
$statement->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
$statement->bind('John', 'H.", 'Smith', 25);
?>
In the first example: You can pass as much or as little to the binding to be done, so that bind() might be called in only one line in your entire application. This scales well.
In the second example: You must write one bind() statement for every possible group of insertions for every possible record in your database. This scales poorly.

Categories