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.
Related
This question already has answers here:
PHP - Using PDO with IN clause array
(9 answers)
Closed 24 days ago.
I did have this code working but it was not secure from SQL injection so I tried to update it. The user submit filter requirements which come from check boxes, I take them from post, and replace them with ?, I then implode, and bind them back together for the IN clause.
I feel like I am a couple of lines of code away from getting this. What am I doing wrong? I have echoed out after implode and it shows the right amount of ? corresponding to the selections. Is the problem in my execute statement?
if(isset($_POST['songgenre'])){
$songgenre = $_POST['songgenre'];
$placeholderssonggenre = array_fill(1, count($songgenre), '?');
$songgenrefilter = implode(',', $placeholderssonggenre);
}else{
$songgenre ='';
$genreempty = '';
}
$sql = "SELECT * FROM music_db WHERE songgenre IN ($songgenrefilter)";
$stmt = $conn->prepare($sql);
$res = $stmt->execute($songgenre);
if ($res !== FALSE) {
$results = $res->rowCount();
echo($results);
} else {
echo "Code Failed";
}
I know really how frustrating it could be. Well, it seems that following things are missing there. Try it:
When you're using the IN clause, you need to pass an array of values to the execute() method, not a single variable. This means that you should be passing $songgenre directly to the execute() method, instead of using it to create the $placeholderssonggenre variable.
You should bind the parameters to the statement before executing it, not after. You can use the bindValue() method to bind the values to the placeholders in the query.
When you execute the statement, you should use the fetchAll() method to retrieve the rows, instead of rowCount().
Here's the correct code:
if(isset($_POST['songgenre'])){
$songgenre = $_POST['songgenre'];
$placeholders = array_fill(0, count($songgenre), '?');
$placeholders = implode(',', $placeholders);
} else {
$songgenre ='';
$placeholders = '';
}
$sql = "SELECT * FROM music_db WHERE songgenre IN ($placeholders)";
$stmt = $conn->prepare($sql);
foreach ($songgenre as $i => $value) {
$stmt->bindValue($i + 1, $value);
}
$stmt->execute();
$results = $stmt->fetchAll();
Another option more elegant
if(isset($_POST['songgenre'])){
$postfilter = [
'songgenre' => ['filter' => FILTER_SANITIZE_STRING] // always sanitize
];
$filter_post_array = filter_var_array($_POST, $postfilter);
$filtered = $filter_post_array['songgenre'];
} else {
$filtered = [];
}
$sql = "SELECT * FROM music_db WHERE songgenre IN (:songgenrefilter)";
$stmt = $conn->prepare($sql);
$stmt->bindValue(':songgenrefilter',implode(',',$filtered);
$stmt->execute();
$result = $stmt->fetchAll();
I have some long MySQL table whose design is not totally fixed yet. So, occasionally, I need to add/delete some columns. But, every time I alter the table, I must re-write all the line dealing with bind_result(). I am looking for a solution which makes this change easy.
Assume I currently have a table with columns like col_a, col_b, col_c, ..., col_z. So, I use the bind_result() to store result values as the manual says.
$res = $stmt->bind_result($a, $b, $c,..., $z);
But, if I change the table design, I must change parameters of all the lines dealing with this bind_result() to match the new MySQL table.
Is there any technique like following?
// Some php file defining constants
define("_SQL_ALL_COLUMNS", "\$a, \$b, \$c, \$d, ... \$z");
// Some SQL process in in other php files
stmt->execute();
$res = $stmt->bind_result(_SQL_ALL_COLUMNS);
So, I don't need to worry about a change of the number of the parameters in other files as long as I once define them correctly somewhere. Of course, I already found that my attempt in the previous example was not a right way.
Is there any good solution for this type of situation?
Use call_user_func_array() to dynamically set the number of parameters:
function execSQL($con, $sql, $params = null)
$statement = $con->prepare($sql);
if (!$statement){
// throw error
die("SQL ERROR: $sql\n $con->error");
}
$type = "";
$arg = array();
if ($params && is_array($params)){
foreach($params as $param){
if (is_numeric($param)){
$type .= 'd';
continue;
}
$type .= 's';
}
$arg[] = $type;
foreach($params as $param){
$arg[] = $param;
}
call_user_func_array(array($statement,'bind_param'), refValues($arg)); // php 7
}
$res = $statement->execute();
if (!$res){
die("Looks like the Execute Query failed.\n\nError:\n{$statement->error}\n\nQuery:\n{$sql}\n\nParams:\n{".implode(",", $arg)."}");
}
return $con->insert_id;
}
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) { //Reference is required for PHP 5.3+
$refs = array();
foreach($arr as $key => $value){
$refs[$key] = &$arr[$key];
}
return $refs;
}
return $arr;
}
You can use it by calling the function execSQL with an array of parameters:
$result = execSQL($connection,$sql,["a","b","c","..."]);
What this does is check the data type of the parameters and appends to the $type variable, which will then be passed to the bind method as first parameter.
This is a better explanation of the problem I woefully tried to explain previously .
I wish to execute multiple queries that all use the same prepared statement, like so (working code):
$params = [
['age'=>10,'id'=>1], ['age'=>12,'id'=>2],
];
$param_types = 'ii';
$sql_template = "UPDATE mytable SET age = ? WHERE id = ?";
$stmt = $mysqli->prepare($sql_template);
$stmt->bind_param($param_types, $age, $id);
foreach($params as $param):
$age = $param['age'];
$id = $param['id'];
$stmt->execute();
endforeach;
I'd like to put this logic in a function, and use it like so:
queries_from_template($sql_template, $params, $param_types);
I'm stuck trying to figure out how to write the function given that I don't know what $params will look like. Here is what I have so far:
function queries_from_template($sql_template,$params,$param_types){
//$mysqli is a handle to a live mysqli DB connection
$stmt = $mysqli->prepare($sql_template);
//build the array that holds the arguments of $stmt->bind_param
//result will be eg: ['ii', 10, 1]
$bind_param_args = array_merge([$param_types],array_values($params[0]));
//call bind_param with a dynamic number of arguments
call_user_func_array([$stmt,"bind_param"],$bind_param_args);
foreach($params as $param):
/* THIS IS WHERE I'M STUCK*/
// I need a handle to each of the parameters that were bound with
// bind_param so that I can set them to the correct value
// on each loop before I execute.
// Remember I don't know how many parameters there are
//run query with current value of parameters
$stmt->execute();
endforeach;
//todo: free results, close connection, disconnect
}
// Associated sets
$params_sets = [
['age'=>10,'id'=>1],
['age'=>12,'id'=>2],
];
// Param names in right order
$param_names = ['age', 'id'];
// Param types in right order
$param_types = 'ii';
// SQL template
$sql_template = "UPDATE mytable SET age = ? WHERE id = ?";
$stmt = $mysqli->prepare($sql_template);
// Ok, let's do it!
foreach ($params_sets as $params) {
// Collecting parameters for bind_param function
// You need to do it every iteration!
// First parameter is $param_types
$bind_params = [$param_types];
// Now let's add every parameter in right order using $param_names
foreach ($param_names as $param_name) {
$bind_params[] = $params[$param_name];
}
// Ok! Call bind_param method from $stmt object with $bind_params as parameters
call_user_func_array([$stmt, 'bind_param'], $bind_params);
// And execute query
$stmt->execute();
}
Good luck!
Since the specific var names used to refer to parameters in $stmt->bind_param(...) don't matter, I changed the input from an associative array to an indexed array
Before:
$params = [
['age'=>10,'id'=>1], ['age'=>12,'id'=>2],
];
Now:
$params = [
[10,1], [12,2],
];
This makes it easier to loop through parameters on each query. Below is my solution:
/*
* Executes multiple queries from the same prepared statement
*/
function queries_from_template($sql_template, $params, $param_types){
$stmt = $mysqli->prepare($sql_template);
$handles = [];//holds references to parameters
for($i=0; $i<count($params[0]);$i++):
$varname = "param_$i";
$$varname = null; //define variables $param_0, $param_1...
$handles[] = &$$varname; //store references to the new variables
endfor;
//call $stmt->bind_param: bind to the new variables
$bind_param_args = array_merge([$param_types],$handles);
call_user_func_array([$stmt,'bind_param'],$bind_param_args);
foreach($params as $param):
foreach($handles as $index => &$handle):
// assign the values for the current execute loop
// to the created vars ($param_0, $param_1...)
$handle = $param[$index];
endforeach;
$stmt->execute(); //execute, todo: error handling
endforeach;
$stmt->close(); $mysqli->close();
}
I'm trying to create a reusable code in PDO.
here's my code.
$myClass = new main_c();
$condition = "email_address = :email_address AND password = :password";
$array = array('email_address' => 'yiihii#yahoo.com', 'password' => '98467a817e2ff8c8377c1bf085da7138');
$row = $myClass->get('user', $condition, $array, $db);
print_r($row);
Here's my function.
public function get($tablename, $condition, $array, $db){
$stmt = $db->prepare("SELECT * FROM $tablename WHERE $condition");
foreach($array as $k=>$v){
$stmt->bindParam(":$k", $v);
}
try{
$stmt->execute();
}catch(PDOException $e){
$error = new main_c();
echo $error->error_handling($e);
}
return $row=$stmt->fetch(PDO::FETCH_ASSOC);
}
I've tried omitting the AND in the condition and just put a single where clause and it worked. I think there's a problem in the foreach. i'm not sure though.
You are not binding values, but parameters, so in your loop you are only binding one variable $v to key $k. Twice. And by the time you execute your query these variables will contain the values of the last iteration of the loop.
You would need to change bindParam() to bindValue().
However, as you are not using the third parameter of bindParam() / bindValue() - forcing a data type - you can skip that whole loop and do:
try {
$stmt = $db->prepare("SELECT * FROM $tablename WHERE $condition");
$stmt->execute($array);
...
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