Retrieving PHP Prepared Statement field data - php

I am using the Mysqli extension and a PHP Prepared Statement SELECT; I don't know how many fields I have in the SELECT until I after I do the
$stmt->execute();
$fieldcnt = $stmt->field_count;
Because this, I am having problems doing a
$stmt->bind_result(list of parms);
Namely, since I don't know how many fields have been returned, I don't know how to contruct the "list of parms."
So, I need some advice on how I access the fields returned;

Don't use bind_result, and use fetch_assoc().
This will return an associated array for each row:
while ($row = $stmt->fech_assoc()) {
print_r($row);
}

You can leverage call_user_func_array() to accomplish this task -- if you absolutely must use bind_result().
// create an array the size of the number of parameters
$params = array_fill(0, $fieldcnt, null);
// call bind_result, resulting in every column of the result to be
// bound to a value in $params
call_user_func_array(array($stmt, 'bind_result'), $params);
// take a look at all the params
print_r($params);

Related

Dynamically generate type definition string for prepared statement

I'm working on a script that is essentially loading data from an API into a local MySQL database. The values are variable depending on what is returned by the API.
So far everything is working just fine up until I try to actually insert the rows into the MySQL db. Specifically, I know I should be using prepared statements, but I'm having trouble when I try to bind the variables to the prepared statement. When I try to run the below code, I get:
PHP Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables in /opt/awn2sql/functions.php on line 212
Here's the code in question:
$readingValues = array_values($read); //array of just the values in the original array
array_push($readingValues, $devicemac); //add the MAC address of the device that recorded the reading to the array
$columns = implode(", ",$readingTypes); //create a string of column names to build the SQL query
$valuesCount = count($readingValues); //get a count of the values to fill an array with placeholders
$stmt_placeholders = implode(',',array_fill(0,$valuesCount,'?')); //fill an array with placeholders (i.e. ?,?,?) - see above
$stmt_param = null; //$stmt_param will hold the type definitions string for binding the
foreach ($readingValues as $param) { //iterate through each value in the $readingValues array, get the type, and add it to the type definitions string
if (gettype($param) == 'integer')
{
$stmt_param = $stmt_param.'i';
}
else if (gettype($param) == 'double')
{
$stmt_param = $stmt_param.'d';
}
else if (gettype($param) == 'string')
{
$stmt_param = $stmt_param.'s';
}
else if (gettype($param) == 'blob')
{
$stmt_param = $stmt_param.'b';
}
else
{
echo "Invalid data type!";
}
}
$val_insert_query = "INSERT INTO ".$config['mysql_db'].".readings (".$columns.") VALUES (".$stmt_placeholders.");"; //Template for the query
$stmt=$mysqli->prepare($val_insert_query); //Prepares the template for the query for binding, prepared statement becomes $stmt
echo ($stmt_param." (".strlen($stmt_param).")\n"); //for debugging, echo the type definiton string and get its length (which should match the number of values)
echo (count($readingValues)); //count the number of values, which should match the number of elements in the type defintion string
$stmt->bind_param($stmt_param, $readingValues); //Binding
$stmt->execute(); //execute the statement
I freely admit that I'm a bit of a newbie at this, so I'm open to any and all suggestions on how to do this better. For what it's worth, there's never any direct user input, so I'm relatively unconcerned about security concerns if that makes a difference in how best to approach this.
Thanks in advance!
bind_param() actually takes variable arguments, not an array argument. But modern PHP has syntax for turning an array into multiple scalar arguments:
$stmt->bind_param($stmt_param, ...$readingValues); //Binding
This is equivalent to passing the array elements as individual arguments:
$stmt->bind_param($stmt_param, $readingValues[0], $readingValues[1],
$readingValues[2], etc.);
But that's awkward if you don't know how many elements are in the array.
FYI, I like to use PDO instead of mysqli. You don't have to bind anything, just pass the array of values as the argument to execute():
$stmt=$pdo->prepare($val_insert_query);
$stmt->execute( $readingValues );
I find PDO to be a lot easier. The reason to use mysqli is if you have a lot of legacy code from the mid-2000's that you need to adapt. If you're just starting out, you have no old code. So you might as well adopt PDO to start with.
There's a good tutorial for PDO: https://phpdelusions.net/pdo/

PHP function to use prepared SQL statements - using implode to enter values of array separated by commas as arguments for function

I have recently asked some questions about security against SQL injection vulnerabilities. I decided to make a function that would do a sql query using a prepared statement so I didn't have to write out so many lines of code for every query:
function secure_sql($query, $values, $datatypes){
global $link;
$stmt = mysqli_prepare($link, $query);
foreach($values as &$value){
$value = mysqli_real_escape_string($link, $value);
mysqli_stmt_bind_param($stmt, substr($datatypes, 0), $value);
$datatypes = substr($datatypes, -(strlen($datatypes)-1));
}
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, implode(", ", $values));
$results = mysqli_stmt_get_result($stmt);
return $results;
}
where query is the prepared query (with ?s), $values is an array of all the variables replacing the placeholder ?s (in order) and $datatypes is a string containing all the data types for the variables. $link is a database connection.
So I have two main questions.
1) It is not working. I think this must be because of implode maybe not being used correctly in this context. What would I use instead? I can't use call_user_func_array because I also need to have $stmt as an argument. I have tried using array_unshift to add $stmt to the beginning of the argument, but it doesn't work.
2) If I do get it to work, what could be done to improve it? I am still a PHP and SQL beginner.
EDIT: I have solved the problem now. Thank you all for your helpful comments and answers!
Instead of directly giving you the code I came up with, I feel that explaining it is necessary, seeing how some of your ways of thinking are rather incorrect.
Firstly, the use of mysqli_stmt_bind_param() confuses me - your second argument expression (substr($datatypes, 0)) returns a value of all the datatypes, yet you are only binding one. What I think you meant to put is:
mysqli_stmt_bind_param($stmt, $datatypes[0] /* <-- retrieves the first character */, $value);
But more importantly, you should only call mysqli_stmt_bind_param() once, which gives you some bigger difficulties... To omit the foreach-loop, how about call_user_func_array()? (Actually, it's very possible to keep $stmt as an argument):
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $values));
//calls mysqli_stmt_bind_param($stmt, $datatypes, $values[0], $values[1]... values[n]);
If you're confused by the thing above, look at it in this way:
call_user_func_array //a call to mysqli_stmt_bind_param, with all the appropriate parameters
(
"mysqli_stmt_bind_param", //the function to call
array_merge //an array consisting of the statement, the datatype and all the values
(
array($stmt, $datatypes), //statement and datatype-parameters
$values //all the values
)
);
This does, however, require your $values-array to consist of references, as the mysqli_stmt_bind_param expects your values to be so. If you still want to pass them as values into your function, you could add this, and later pass $ref_values into the call_user_func_array() function:
foreach ($values as &$value) $ref_values[] = &$value;
Now we come to the use of implode(), which also is incorrect. To reference from the PHP-manual:
Implode (Return Value):
Returns a string containing a string representation of all the array elements in the same order, with the glue string between each element.
mysqli_stmt_bind_result (Second-Nth Parameter):
The variable to be bound.
So what you're attempting to do here is to make a string returned by implode() a variable, which makes no sense. Although luckily, mysqli_stmt_get_result() returns an object which is fetched after execution, meaning that the bind_result-function isn't needed. So try removing that line.
In all, I would re-write the code like this:
function secure_sql($query, $values, $datatypes) {
global $link;
$stmt = mysqli_prepare($link, $query);
foreach ($values as &$value) $ref_values[] = &$value;
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $ref_values));
mysqli_stmt_execute($stmt);
return mysqli_stmt_get_result($stmt);
}
To me, it sounds like that should work, but if it doesn't, tell me (I might be missing something or thinking incorrectly somewhere).
To answer the second question, it all looks good, except that I wouldn't advice you to use mysqli_-functions, as they will get removed eventually (as said in the PHP-manual). If you're planning to use objects instead, most of it is similar when it comes to my changes (apart from the fact that you need to use object-properties and object-methods with them instead...), except the call_user_func_array() function. Luckily though, calling a method with it is possible as well, by specifying an array as the first parameter, consisting of the object and the method name (call_user_func_array($prepared_obj, "bind_param") ...)).
Edit: Considering how necessary it is, I made a function that does the same thing, but works on an mysqli object instead:
function secure_sql($query, $values, $datatypes) {
global $mysqli_object; //declared with $mysql_object = new mysqli(...)
$stmt = $mysqli_object->prepare($query);
foreach ($values as &$value) $ref_values[] = &$value;
call_user_func_array(array($stmt, "bind_param"), array_merge(array($datatypes), $ref_values));
$stmt->execute();
return $stmt->get_result();
}

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.

Why doesn't this prepare statement work in MYSQLI?

I created this code:
$statement = $db->prepare("SELECT * FROM phptech_contact");
$statement->execute();
$result = $statement->result_metadata();
$object = $result->fetch_object();
print_r( $object );
When I run it, it doesn't work. Can anybody tell me why it doesn't work?
I have 20 rows in this table so data should be returned.
From http://ch.php.net/manual/en/mysqli-stmt.result-metadata.php
Note: The result set returned by mysqli_stmt_result_metadata() contains only metadata. It does not contain any row results. The rows are obtained by using the statement handle with mysqli_stmt_fetch().
As long as you don't need this meta data you don't need to call this method.
$statement = $db->prepare("SELECT fld1, fld2 FROM phptech_contact");
$statement->execute();
$stmt->bind_result($fld1, $fld2);
while ($stmt->fetch()) {
echo "$fld1 and $fld2<br />";
}
But I really dislike the mysqli extension. PDO is much cooler ... ;-)
$db = new PDO('...');
$stmt = $db->prepare("SELECT fld1, fld2 FROM phptech_contact");
$stmt->execute();
while ($obj = $stmt->fetchObject()) {
// ...
}
or
$objs = stmt->fetchAll(PDO::FETCH_OBJ);
if you're trying to get the rows from the database, the function you need is mysqli_stmt::fetch(), not mysqli_stmt::fetch_metadata()
You're also missing a few steps. When using prepared statements, you must specify the fields you would like to return instead of using the star wildcard, and then use mysqli_stmt::bind_result() to specify which variables the database fields should be placed in.
If you're more familiar with the original MySQL extension, prepared statements have a different process to use. If your select statement has a parameter (eg., "WHERE value=?") prepared statements are definitely recommended, but for your simple query, mysqli:query() would be sufficient, and not very different from the process of mysql_query()
I believe the problem is that mysqli_stmt::result_metadata() returns a mysqli_result object without any of the actual results — it only holds metadata.
So what you want to do is use $result = $statement->bind_result(...) and then call $result->fetch() repeatedly to get the results.
One of the comments under the bind-result() article shows how to do this for a query like yours, where you don't necessarily know all of the columns being returned.

PHP and MYSQLi - Bind parameters using loop and store in array?

Below is my code for the function I am using to retrieve multiple data from my table, but I would like to use bind_result($array[0],...,..) to be generated automatically depending on number of fields i am selecting in the query.
for example..
$query=select a,b,c,d,e from table;//selecting 5 fields
......
$stmt->execute();$stmt->bind_result($retrieve[0],$retrieve[1],$retrieve[2],$retrieve[3],$retrieve[4]);
(the bind_result for 5 values should be automatically generated)
Help will be appreciated ...Thanks
$query="SELECT comment, userid,UNIX_TIMESTAMP(dtime)
FROM comment_updates
WHERE updateid=46546
ORDER BY dtime DESC
LIMIT 10 ";
if($stmt = $this->conn->prepare($query)) {
$stmt->execute();
$stmt->bind_result($comments[0],$comments[1],$comments[2]);
$i=0;
while($stmt->fetch()){
$i++;
$name='t'.$i;
$$name = array($comments[0],$comments[1],$comments[2]);
}
return array($i,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10);
$stmt->close();
}
This should get you started:
http://php.net/manual/en/mysqli-stmt.result-metadata.php
This will get you the number of fields in your resultset via mysqli_num_fields().
This should be the size of your $retrieve array.
As bind_result doesn't take an array as an argument, you'll need to use call_user_func_array to achieve this:
call_user_func_array(array($stmt, 'bind_result'), $retrieve_references);
$retrieve_references should be an array of references to the elements in $retrieve. Using $retrieve itself in call_user_func_array will trigger an error.

Categories