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

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.

Related

PDO ERROR: invalid input syntax for type boolean and integer - if empty input

I am using PDO for a dynamical update query for PostgreSQL. The problem is, all the values are transmitted to the database as strings, which throws an error for the supposed boolean (edit: and integer) values:
PHP Fatal error: Uncaught PDOException: SQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for type boolean: »«
Edit: The problem appears whenever the boolean or integer fields get an empty input. What can I do to make the PDO transfer the inputs as "null" to the database instead of empty strings that throw the data type errors?
Below is my update method, I have tried to do the binding inside a loop and add an explicit type casting for the two supposed boolean values:
public function updateMod($array)
{
$keys = array_keys($array);
$vals = array_values($array);
$setStr = "";
foreach ($keys as $key) {
$setStr .= "$key = :$key, ";
}
$setStr = rtrim($setStr, ", ");
$pdoConn = DbConnection::openConnection();
$stmt = $pdoConn->prepare("UPDATE mods SET $setStr WHERE id = :id;");
$i = 0;
foreach ($keys as $key) {
// type casting the two supposed booleans:
if($key === 'g_exists' || $key === 'w_exists') {
$stmt->bindValue($key, $vals[$i], PDO::PARAM_BOOL);
} else {
$stmt->bindValue($key, $vals[$i]);
}
$i++;
}
$stmt->bindValue('id', $this->Id());
if ($stmt->execute()) {
return true;
} else {
return false;
}
}
But I keep getting the same error.
Does anyone have an idea how to solve this?
You are creating "the setting part" of the query in a loop irregardless of whether there are any values to write or not.
So the solution would be to check whether there is a value for given key.
A simple solution might me to call array_filter on the array containing the data - this way the empty values and their keys are being removed before processing
Try to start the method like this:
public function updateMod($array)
{
$array = array_filter($array); # This line is important
$keys = array_keys($array);
$vals = array_values($array);
...
array_filter documentations says:
If no callback is supplied, all empty entries of array will be removed
Ok, I just found the answer:
My mistake was that I was trying to typecast to booleans/integers which didn't work on empty strings. So I had to filter the empty strings themselves as well and cast them directly to null values. So, the above binding foreach loop should look like this instead:
foreach ($keys as $key) {
if($key === 'g_exists' && $vals[$i] === '') { // filter key AND empty value
$stmt->bindValue($key, $vals[$i], PDO::PARAM_NULL); // cast to null
} else {
$stmt->bindValue($key, $vals[$i]);
}
$i++;
}

How can I pass many columns to bind_result() with more flexibility?

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.

Mysqli prepared statement : several WHERE clauses and WHERE IN (Array) [duplicate]

This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Closed 11 months ago.
I need to run the query below. It asks me a lot of trouble. In fact, I have several "WHERE" conditions, one that requires the decomposition of an Array.
This issue helped me but it doesn't have several conditions "WHERE" .
$array = (1,2,3,4,5,6,7,8,9,10);
$clause = implode(',', array_fill(0, count($array), '?'));
if($request = $this->getConnexion()->prepare('SELECT col1, col2 FROM table WHERE col1 IN ('.$clause.') AND col2>=?') or die(mysqli_error($this->getConnexion()))) {
// The problem starts here
call_user_func_array(array($request, 'bind_param'), $array);
$request->bind_param('i', $this->getTime());
// Until here
$request->execute();
$request->bind_result($col1, $col2);
$request->store_result();
// Following the code
}
The important thing here is that you are calling bind_param() just once, with an array containing all of the parameters you'll need to bind, so your solution will be to just add the additional WHERE clause parameter onto your $array of values to bind. The IN() clause isn't a special case requiring call_user_func_array() separated from other parameters. You call it on all of them.
Something is missing though - bind_param()'s first parameter is a string of data types. All your types are i, so you'll need to use str_repeat() to create that.
// Eventually, this array will contain the other params too
$array = (1,2,3,4,5,6,7,8,9,10);
// This creates a string of ?,?,?,?... for the IN () clause
$clause = implode(',', array_fill(0, count($array), '?'));
// Add the additional value onto the $array array
// so the last param is bound with the others.
$array[] = $this->getTime();
$types = str_repeat('i', count($array));
// The params passed to call_user_func_array() must have as references, each subsequent value. Add those in a loop, but start with the $types string
$params = array($types);
foreach ($array as $key => $value) {
$params[] = &$array[$key];
}
if($request = $this->getConnexion()->prepare('SELECT col1, col2 FROM table WHERE col1 IN ('.$clause.') AND col2>=?') or die(mysqli_error($this->getConnexion()))) {
// Then bind_param() is called on all parameters
// using the $params array which includes types and param references
call_user_func_array(array($request, 'bind_param'), $params);
// Execute & fetch.
$request->execute();
$request->bind_result($col1, $col2);
$request->store_result();
// Following the code
}

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

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.

Why doesn't PDO::bindValue() fail in this instance?

I've written the following function to construct and execute an SQL statement with key-value bindings. I'm using bindValue() to bind an array of key-value pairs to their corresponding identifiers in the SQL string. (The echo statements are for debugging).
public function executeSelect($sql, $bindings = FALSE)
{
$stmt = $this->dbPDO->prepare($sql);
if ($bindings)
{
foreach($bindings as $key => $value)
{
$success = $stmt->bindValue($key, $value);
echo "success = $success, key = $key, value = $value<br />";
if (!$success)
{
throw new Exception("Binding failed for (key = $key) & (value = $value)");
}
}
}
echo "Beginning execution<br />";
if ($stmt->execute())
{
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else
{
return FALSE;
}
}
The input to this function is as follows:
$stmt = "SELECT * FROM test WHERE id = :id";
$bindings = array(":id" => "3", ":Foo" => "Bar");
On the second loop through the $bindings array, I'd expect $success to evaluate to false, thus throwing the custom Exception since "Bar" cannot be bound to ":Foo", since ":Foo" doesn't exist in the input SQL.
Instead, $success evaluates to true (1) for both key-value pairs in the $bindings array, and a PDOException is thrown by ->execute() "(HY000)SQLSTATE[HY000]: General error: 25 bind or column index out of range"
Why isn't bindValue returning false?
Because it works this way.
it throws an error not at bind but at execute. That's all.
So, there is no need in loop and you can make your method way shorter.
public function executeSelect($sql, $bindings = FALSE)
{
$stmt = $this->dbPDO->prepare($sql);
$stmt->execute($bindings);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
There is no need in checking execute result either, I believe.
In case of error it will raise an exception already.
By the way, I'd make several helper functions based on this one, returning scalar value and single row. They are mighty helpful. Though I find named placeholders a bit dull. Compare this code:
$name = $db->getOne("SELECT name FROM users WHERE group=?i AND id=?i",$group,$id);
vs.
$sql = "SELECT name FROM users WHERE group=:group AND id=:id";
$name = $db->getOne($sql,array('group' => $group, 'id' => $id));
named require 2 times more code than anonymous.
A perfect example of WET acronym - "Write Everything Twice"

Categories