I have a similar problem like the author of this question:
MySQLI binding params using call_user_func_array
But before my question get duplicated tag, the solution didn't worked out for me.
That actually is my code:
// in classfoodao.php
function updateClassfoo(Classfoo $classfoo){
$values = array();
global $conn,$f;
$pattern = "";
/* updateFields get all defined attributes in classfoo object and then write in the $sql string
* A prepared statement only for the not null attribs,
* and also put the attribs in the same order in the $values array.
* The same are done for the $pattern string.
* $values and $pattern are passed by reference.
*/
$sql = $classfoo->updateFields($values, $pattern);
if (!empty($values) && !empty($pattern)) {
$stmt = $conn->prepare($sql);
$temp = array($pattern);
for ($i = 0, $count = count($values); $i < $count; $i++) {
$addr = &$values[$i];
array_push($temp, $addr);
}
call_user_func_array(array($stmt, "bind_param"), $temp);
} else {
return true;
}
}
And I still getting this Warning:
PHP Warning: Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
Following with the error:
Execute failed on update: (2031) No data supplied for parameters in prepared statement
I'm not using any framework for PHP, how can I create an Array of references to solve this problem?
I still get the feeling we do not have the whole picture here, however I think there is enough to formulate a sufficient answer.
It sounds like you want a function that takes a mysqli_stmt string and an array as it's 3 parameters and returns a parameterized SQL statement. In code your method signature would look like
/**
* Builds a parameterized Sql query from the provided statement, data type pattern and params array.
*
* #param string $stmt The query string to build a parameterized statement from
* #param string $pattern The data type pattern for the parameters i.e. sssd for 3 strings and an interger.
* #param array $params A sequential array of the parameters to bind to the statement.
*
* #return mysqli_stmt The parameter bound sql statement ready to be executed.
*/
public function bind(string $stmt, string $pattern, array $params) : mysqli_stmt
You would call such a method as such
$stmt = bind(
'INSERT INTO myTable (str_field, str_field_2, int_field) VALUES (?, ?, ?)',
'ssd',
['val1', 'val2', 1337]
);
$result = $stmt->execute();
The implementation should be as trivial as this
public function bind(string $stmt, string $pattern, array $params) : mysqli_stmt {
$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');
$preparedStmt = $mysqli->prepare($stmt);
$preparedStmt->bind_param($pattern, ...$params);
return $preparedStmt;
}
I of course would suggest you use dependency injection rather than creating a new mysqli object every time the function is called and you should do some error checking in regards to the $pattern and $param counts. There's still a lot I can't see but I hope this gets you on your way.
Related
As bind_param($types, $var1, $var2, $var3,..) method of mysqli_stmt class it gets just a series of $variables after second parameter (I want to pass array there), and the number of $variables is unknown in my case, I want to use Reflection in my insert($data) function. http://php.net/manual/ru/mysqli-stmt.bind-param.php
I do not show the unnecessary part of my function, to avoid confusion...
public function insert($data)
{
$types = 'sss';
$values = array_values($data);
Removed unrelated code
$ref = new ReflectionClass($this->stmt);
$method = $ref->getMethod("bind_param");
//array_unshift($values,$types); 1-option
$values = array($types,'alex','alex#code.com','cats'); 2-option
$method->invokeArgs($this->stmt, $values);
$done = $this->stmt->execute();
$this->stmt->close();
return $done;
}
As shown in
$method = $ref->getMethod("bind_param");
$method->invokeArgs($this->stmt, $values);
In this part I use Reflection to pass array to second parameter of bind_param() method of $this->$stmt object.
$method->invokeArgs($this->stmt, $values);
It doesn`t make mysqli insert into table with 1-option.
But mysqli inserts data when I use 2-option. Why? I have to use with 1-option as number of parameters is unknown.
How can I benefit from Reflection and mysqli it?
What is the difference between those two options(arrays)?
You can use splat operator (PHP version >= 5.6):
public function insert($data)
{
$stmt = $this->link->prepare($sql);
$stmt->bind_param(str_repeat('s', count($data), ...$data);
$stmt->execute();
}
This question already has an answer here:
Bind multiple parameters into mysqli query
(1 answer)
Closed 9 years ago.
Right now I'm currently attempting to make a MySQLi class to make typing code easier and so it looks cleaner and is more usable overall.
I'm attempting to write a function which will execute a query with a prepared statement. My dilemma is this:
public function safeRead($query, $data, $params)
{
$stmt = $mysqli->prepare($query);
$stmt->bind_param($params, $data);
$stmt->execute();
$result = $stmt->get_result();
$check = $result->fetch_assoc();
}
I of course want to execute a query, as you can see. My problem lies with the $data variable. How can I/is it possible to pass data, as a string and possibly convert to an array or something usable so it can be used with bind_param ?
bind_param prototype looks like this:
bool mysqli_stmt::bind_param ( string $types , mixed &$var1 [, mixed &$... ] )
so it accepts a string of types sssd, and a bunch of variables for those types
Assuming you are passing in the correct type of variables
$stmt->bind_param($params, $data);
A way to do this would be
public function safeRead($query, $data, $params)
{
$stmt = $mysqli->prepare($query);
$params = str_split( $params ); // Split the params 'sssd'
foreach( $data as $k => $v ) {
$stmnt->bind_param( $params[$k], $data[$k] );
}
$stmt->execute();
$result = $stmt->get_result();
$check = $result->fetch_assoc();
}
This is untested.
Edit
Since the second parameter of bind_param is passed by reference you MAY need to create an intermediate variable before binding, instead of binding the array item.
foreach( $data as $k => $v ) {
$var_name = 'var'.$k;
$$var_name = $v;
$stmnt->bind_param( $params[$k], $$var_name );
}
but im not 100% sure.
I was reader about PDO , and I was wondering what is the deference between those two methods:
public function query($sql)
{
$req = $this->db->prepare($sql);
$req->execute();
return $req->fetchAll(PDO::FETCH_OBJ);
}
public function query($sql, $data = array())
{
$req = $this->db->prepare($sql);
$req->execute($data);
return $req->fetchAll(PDO::FETCH_OBJ);
}
In the second method, execute has an empty array as a parameter and the first one doesn't, what is the role of using an empty array as a parameter for execute ?
The array is only empty by default. You can pass values in that array and they will be inserted into your SQL statement appropriately (ie - array key=>field name).
Defining an empty array in the function parameters states that this is an optional parameter and you are not forced to pass it - only when it is relevant. For example, when performing an INSERT command. If you don't pass any value to the $data parameter, it's default value will simply be an empty array.
An example of using default parameters -
function saySomething($text="Hello World!"){
echo $text;
}
saySomething(); // will echo out the default "Hello World!"
saySomething("Goodbye World!"); // will echo out "Goodbye World!" as specified.
First one lets you to run a query without parameters.
Second one lets you to run a query either with parameters or without:
$data = $db->query("SELECT * FROM table");
$data = $db->query("SELECT * FROM table WHERE id=?",array($id));
both works.
You can add you parameter bindings in an array instead of using the bindParam() function beforehand.
for instance you wanna select something by id
$stmt = $dbh->prepare("SELECT * FROM `something` WHERE `id` = ?");
$stmt->execute(array($id));
is the same as
$stmt = $dbh->prepare("SELECT * FROM `something` WHERE `id` = ?");
$stmt->bindParam(1, $id, PDO::PARAM_INT, 11);
$stmt->execute();
Though for the bindParam function you can check better,
Check PHP Manual PDO::excute()
The Problem
So I'm writing my web based application and it dawns on me "Durr, your stuff is wide open to SQL injection and whatnot! Rewrite db class!"
I'm currently re-writing my $db class and I am having a significant amount of trouble understanding how I'm supposed to implement prepared statements.
Previously...
I used to use something like this:
$db->runQuery("SELECT * FROM someTable WHERE someField = '$var1'");
while ($result = mysql_fetch_array($db->result){
// ... ugh, tedious
}
Invariably, when performing select statements, I'm grabbing an array, and looping through the results.
I understand that...
I should be burnt at the stake for using non-prepared statements in MySQL.
I have to let mysql know what type of parameter each variable is. (Or do I)?
I'd like to...
Be able to pass my query, and values to my new function (let's use select as an example) which would then return a result for me to work with (as an assoc. array of values);
$query = "SELECT * FROM someTable WHERE someField = ? AND anotherField = ?";
$params = array($var1, $var2);
$result = $db->doSelect($query, $params);
// Then do all sorts of neat stuff with $result - huzzah!
I'm having trouble with...
Understanding how I would bring all the information together.
How do I present an array of values and have that mushed together with my prepared statement?
With said mushed statement, how do I run it (execute()?) and have it return an array?
I'm sorry if my question is somewhat roundabout, however I'm frazzled from trying to understand it. If more information is required, please let me know and I'll add it.
Here is what I've written for a Prepare/Execute function set. These are of course part of a larger DB object.
/**
* Prepares a query to be run, storing the data in $this->preparedTokens
* Use the following characters to indicate how the data is to be put into SQL statement
* ? -> escaped and quoted (with single quotes) before inserting
* ^ -> inserted as is
* & -> implodes the array escpaping each value
* # -> implodes the array (no escaping)
*
* #param string $sql The SQL statement to prepare
*
* #return int The key of prepare sql query to be passed to $this->Execute()
*/
public function Prepare($sql) {
$tokens = preg_split('/((?<!\\\)[#&?^])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);
// loop through removing any escaped values
foreach ($tokens as $key => $val) {
switch ($val) {
case '?' :
case '&' :
case '#' :
break;
default :
$tokens[$key] = preg_replace('/\\\([#&?^])/', "\\1", $val);
break;
} // switch
} // foreach
$this->preparedTokens[] = $tokens;
end($this->preparedTokens);
return key($this->preparedTokens);
} // function Prepare
/**
* Creates the SQL placing the data in the appropriate places and then runs the sql
*
* #param int $preparedKey The key of the prepared sql
* #param array $data The array of data to put into the query (the count of this array must match that of the prepared query)
*
* #return object false if the $preparedKey does not exist in $this->preparedTokens
* false if count of needed values in sql statement does not equal the number of keys in the data array
* otherwise, the result of $this->Query()
*/
public function Execute($preparedKey, $data) {
if (isset($this->preparedTokens[$preparedKey])) {
$tokens = $this->preparedTokens[$preparedKey];
$query = '';
$dataKey = 0;
$count = 0;
// count the number of tokens we have
$validTokens = array('?', '^', '&', '#');
foreach ($tokens as $val) {
if (in_array($val, $validTokens)) {
++$count;
} // if
} // foreach
// check to ensure we have the same number of tokens as data keys
if ($count != count($data)) {
trigger_error('Query Error: The number of values received in execute does not equal the number of values needed for the query', E_USER_ERROR);
return false;
} // if
// loop through the tokens creating the sql statement
foreach ($tokens as $val) {
switch ($val) {
case '?' :
$query .= "'" . $this->EscapeString($data[$dataKey++]) . "'";
break;
case '^' :
$query .= $data[$dataKey++];
break;
case '&' :
$query .= $this->ImplodeEscape($data[$dataKey++]);
break;
case '#' :
$query .= implode(',', $data[$dataKey++]);
break;
default :
$query .= $val;
break;
} // switch
} // foreach
return $this->Query($query);
} else {
return false;
} // if
} // function Execute
/**
* Runs $this->Prepare() then $this->Execute() for the sql and the data
* Use the following characters to indicate how the data is to be put into SQL statement
* ? -> escaped and quoted (with single quotes) before inserting
* ^ -> inserted as is
* & -> implodes the array escpaping each value
* # -> implodes the array (no escaping)
*
* #param string $sql The SQL statement to prepare
* #param array $data The array of data to put into the query (the count of this array must match that of the prepared query)
*
* #return object returns value from $this->Query() if Execute was successful
* otherwise it'll be false
*/
public function PrepareExecute($sql, $data) {
return $this->Execute($this->Prepare($sql), $data);
} // function PrepareExecute
$this->Query() executes the MySQL statement and then returns different values depending on what the statement is (based on the first 6 characters of the statement, trimmed):
false if failed (use $this->GetError() to get error message)
if successful INSERT, then the insert id
if successful DELETE or UPDATE or REPLACE, then the number of affected rows
if successful SELECT or any other query type, then the Query object
I'm not sure if this is what you are looking for, but it might help.
Forgot to mention this, but most of the ideas came from the Pear::DB class: http://pear.php.net/package/DB
See if you can make use of following.
Let me know if you need a more detailed implementation
call_user_func_array(array($stmt,"bind_result"), $params);
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.