How to convert a string into a usable array [duplicate] - php

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.

Related

PHP 7.0 mysqli - Reference array for call_user_func_array

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.

PHP: "Value Expected To Be A Reference" with Call_User_Func and Bind Params [duplicate]

This question already has answers here:
mysqli bind_param() expected to be a reference, value given
(3 answers)
Closed 11 months ago.
I have been trying to bind an array with a prepared statement for days, I have managed to create the number of ?,? and the i,i these are represented in my code as $params and $type. The problem comes when using call_user_func_array with my code since I haven't managed to make it work, I have tried multiple answers from this site but I haven't got it working.
The full query code is:
$params = implode(',', array_fill(0, count($greaterThan), '?'));
$stmt11 = $mysqli->prepare("SELECT nombre FROM usuarios WHERE id IN (".$params.")");
$type = implode('', array_fill(0, count($greaterThan), 'i'));
$param = implode(",", $greaterThan);
call_user_func_array(array($stmt11, "bind_param"), array_merge(array($type), $greaterThan));
print_r(error_get_last());
$stmt11->execute();
$stmt11->store_result();
$stmt11->bind_result($nombresmayores);
$arraynombresmayores = array();
$stmt11->store_result();
while($stmt11->fetch()){
$arraynombresmayores[] = $nombresmayores;
}
Where $param are the values separated by comas (just in case you need it). The array I'm trying to bind is $greaterThan, the query works perfectly because I have made some debugging. The error the program outputs is:
Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
Finally, the content of the array is:
array(2) {
[0]=>
int(2)
[1]=>
int(4)
}
if your php version is not outdated you can make it much simper
$params = implode(',', array_fill(0, count($greaterThan), '?'));
$stmt = $mysqli->prepare("SELECT nombre FROM usuarios WHERE id IN ($params)");
$types = str_repeat('i',count($greaterThan));
$stmt->bind_param($types, ...$greaterThan);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombresmayores);
$arraynombresmayores = array();
$stmt->store_result();
while($stmt->fetch()){
$arraynombresmayores[] = $nombresmayores;
}
but better yet use PDO:
$params = implode(',', array_fill(0, count($greaterThan), '?'));
$stmt = $pdo->prepare("SELECT nombre FROM usuarios WHERE id IN ($params)");
$stmt->execute($greaterThan);
$arraynombresmayores = $stmt->fetchAll(PDO::FETCH_COLUMN);
just compare this neat code with that awful mess you have now.
I have method i use in an class i made to work mysqli in a fashinable way. The key reside in the foreach loop: Basicaly you create a copy of your array with another one by reference and then bind the referenced array. Hope this helps.
UPDATED :
in your case you need to create an array with your string $type and the array of greateThan. From the temp to the bind array, make sure the keys stay the sames and don't unset your data before having called the bond_param
$type = implode('', array_fill(0, count($greaterThan), 'i'));
//$param = implode(",", $greaterThan); //Not needed
//merge the 'type' as an array and the variables to pass
$temp_params= array_merge([$type],$greaterThan);
//Create a referenced array but dont'reference the 'type' argument
foreach($temp_params as $k=>$v){
if($k===0){
$bind_params[$k]=$temp_params[$k];
}else{
$bind_params[$k]=&$temp_params[$k];
}
}
//use the referenced array
call_user_func_array(array($stmt11, "bind_param"), array_merge(array($type), $bind_params));
Just try to assign that expression to a variable, and bind a variable. Expressions cannot be used as references, so you have to pass a variable.

Can't bind string containing # char with mysqli_stmt_bind_param [duplicate]

This question already has answers here:
mysqli bind_param() expected to be a reference, value given
(3 answers)
Closed 3 months ago.
I have a problem with my database class. I have a method that takes one prepared statement and any number of parameters, binds them to the statement, executes the statement and formats the result into a multidimentional array.
Everthing works fine until I try to include an email adress in one of the parameters. The email contains an # character and that one seems to break everything.
When I supply with parameters:
$types = "ss" and $parameters = array("email#domain.com", "testtest")
I get the error:
Warning: Parameter 3 to
mysqli_stmt_bind_param() expected to
be a reference, value given in
...db/Database.class.php on line 63
Here is the method:
private function bindAndExecutePreparedStatement(&$statement, $parameters, $types) {
if(!empty($parameters)) {
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($statement, $types), &$parameters));
/*foreach($parameters as $key => $value) {
mysqli_stmt_bind_param($statement, 's', $value);
}*/
}
$result = array();
$statement->execute() or debugLog("Database error: ".$statement->error);
$rows = array();
if($this->stmt_bind_assoc($statement, $row)) {
while($statement->fetch()) {
$copied_row = array();
foreach($row as $key => $value) {
if($value !== null && mb_substr($value, 0, 1, "UTF-8") == NESTED) { // If value has a nested result inside
$value = mb_substr($value, 1, mb_strlen($value, "UTF-8") - 1, "UTF-8");
$value = $this->parse_nested_result_value($value);
}
$copied_row[$ke<y] = $value;
}
$rows[] = $copied_row;
}
}
// Generate result
$result['rows'] = $rows;
$result['insert_id'] = $statement->insert_id;
$result['affected_rows'] = $statement->affected_rows;
$result['error'] = $statement->error;
return $result;
}
I have gotten one suggestion that:
the array_merge is casting parameter
to string in the merge change it to
&$parameters so it remains a reference
So I tried that (3rd line of the method), but it did not do any difference.
How should I do? Is there a better way to do this without call_user_func_array?
I wrote the parameter-binding code in Zend Framework's MySQLi adapter. I find the parameter-binding API for MySQLi to be hard to use. It's not the array that needs to be a reference, it's each element of the array.
You can see the code I wrote in the _execute() method in this class:
http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Db/Statement/Mysqli.php
I recommend that you check out PDO. It's far more easy to use. You can bind parameters, but it's even easier to just pass the array of parameter values as an array to the PDOStatement's execute() method.

PDO and SQL IN statements [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 9 years ago.
Im using a sequel for search like this using PDOs
$states = "'SC','SD'";
$sql = "select * from mytable where states in (:states)";
$params = array(':states'=>$states);
and I use my function
$result = $this->selectArrayAssoc($sql, $params);
where my selectArrayAssoc function as following
public function selectArrayAssoc($sql, $params = array())
{
try {
$sth = $this->db->prepare($sql);
$sth->execute($params);
$result = $sth->setFetchMode(PDO::FETCH_ASSOC);
return $sth->fetchAll();
} catch(PDOException $e) {
print $e->getMessage();
//Log this to a file later when in production
exit;
}
}
it does not take the quoted variables, I think it is suppressing, in such cases how to deal with this.
When using prepared statement placeholders (parameter binding) in general, each occurrence of a placeholder holds exactly one variable.
You're trying to pass several. What's happening is basically that your parameters are escaped: Your :states is replaced with '''SC'',''SD''' or '\'SC\',\'SD\'' internally, rather than with just the raw 'SC','SD' that you want.
pinkgothic is absolute correct. But I think you got the problem, that you have an array of 'states' and want work with this array. You've to prepare the Placeholder for each value in the query.
$states = array('SC','SD');
$phArray = array();
$valArray = array();
foreach($ids AS $key=>$value){
array_push($phArray, ':PH' . $key);
$valArray[':PH' . $key] = $value;
}
$sql = 'select * from mytable where states in (' . implode(',', $phArray) . ')';
$params = array($valArray);
$result = $this->selectArrayAssoc($sql, $params);

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