Binding a variable number of values to a prepared statement - php

I'm currently struggling to solve a small problem with MySQLi's prepared statements. I'm trying to write a custom PHP function that takes a few variables and uses them to write some data into a table in my database, via prepared statement.
The issue is that I need the function to accept any number of parameters.
An example of the function in action:
db_insert_secure("Goals", "(Name, Description, Type, ProjectID)", $data);
This is supposed to write all of the info stored in the $data array into the (4) specified rows in the Goals table.
However, because the amount of parameters can change, I can't think of a way to bind them in an efficient manner.
The one idea I had was to use a switch statement that would handle binding different numbers of parameters, but that isn't the most eloquent or efficient method, I'm sure.
The script in its entirety:
function db_insert_secure($table, $columns, $data)
{
$link = db_connect();
$inputData = explode(",", $columns);
$query = "INSERT INTO ".$table." ".$columns." VALUES (";
for ($i = 0; $i < sizeof($inputData); $i ++)
{
$query .= "?";
if ($i != sizeof($inputData) - 1)
{
$query .= ", ";
}
}
$query .= ")";
echo $query;
$check_statement = mysqli_prepare($link, $query);
//mysqli_stmt_bind_param($check_statement, 's', $data[0]);
echo $check_statement;
db_disconnect($link);
}
NOTE: the db_connect and db_disconnect scripts are custom scripts for opening and closing a connection to the database. db_connect simply returns the connection object.
Can anyone think of a solution to this problem that does not involve using eval?

Funnily enough, after struggling with this for the past 12 hours or so, I actually managed to find a solution to the problem within a few minutes of starting this thread.
You can use an array as data for a prepared statement by placing "..." in front of it.
For example, in my case:
mysqli_stmt_bind_param($preparedStatement, 'ssss', ...$data);

Related

How to prepare a statement with a dynamic number of parameters?

This code works but it seems insecure because of concatenating GET parameters into the query. I'm concatenating because I need a dynamic number of parameters in the WHERE clause that can be of different types (IN, normal comparison condition).
How can I prepare a secure statement from a dynamic number of different type WHERE conditions?
class myclass
{
public function index($where_clause = 1)
{
// db connection (using pdo)
$stm = $this->dbh->query("SELECT COUNT(amount) paid_qs FROM qanda $where_clause");
$ret = $stm->fetch(PDO::FETCH_ASSOC);
// do stuff
}
public function gen_where_clause()
{
$where_clause = '';
if (isset($_GET['c']) || isset($_GET['t']))
{
$where_clause = 'WHERE ';
if (isset($_GET['c']))
{
$where_clause .= 'cat = ' . $_GET['c'];
}
if (isset($_GET['t']))
{
if (isset($_GET['c']))
{
$where_clause .= $where_clause . ' AND '
}
$where_clause .= 'tag IN(' . $_GET['t'] . ')';
}
}
return $this->index($where_clause);
}
}
I'll address this question on three fronts: actual correctness of the code, a solution, and better practices.
The Code
This code actually does not work, as mentioned there are very basic syntax error that even prevents it from actually being run at all. I'll assume this is a simplification error, however even the concatenation is wrong: the statement is duplicated each time (.= and the string itself. either of these will work, both will destroy the query)
$where_clause .= $where_clause . ' AND '
Dynamic Number Of Parameters
The problem of having a dynamic number of parameters is interesting, and depending on the needs can be fairly convoluted, however in this case, a fairly simple param concatenation will allow you to achieve a dynamic number of parameters, as suggested by AbraCadaver .
More exactly, when a condition is added to the statement, separately add the sql to the statement, and the values to an array:
$sql .= 'cat = :cat';
$values[':cat'] = $_GET['c'];
You can then prepare the statement and execute it with the correct parameters.
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
Better Practices
As mentioned, the code presented in this question possibly is not functional at all, so let me highlight a few basic OOP principles that would dramatically enhance this snippet.
Dependency Injection
The db connection should be injected through the constructor, not recreated every time you execute a query (as it will, if you connect in the index method). Notice that $pdo is a private property. It should not be public, accessible by other objects. If these objects need a database connection, inject the same pdo instance in their constructor as well.
class myclass
{
private $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
}
The flow
One of these methods should be private, called by the other (public one) that would receive in arguments everything that is needed to run the functions. In this case, there does not seem to be any arguments involved, everything comes from $_GET.
We can adapt index so that it accepts both the sql and the values for the query, but these three lines could easily be transferred to the other method.
private function index($sql, $values)
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($values);
return $stmt->fetchAll();
}
Then the public gen_where_clause (I believe that is wrongly named... it really generates the values, not the clauses) that can be safely used, that will generate a dynamic number of parameters, protecting you from sql injection.
public function gen_where_clause()
{
$sql = "SELECT COUNT(amount) AS paid_qs FROM qanda ";
$values = [];
if (isset($_GET['c']) || isset($_GET['t']))
{
$sql .= ' WHERE ';
if (isset($_GET['c']))
{
$sql .= ' cat = :cat ';
$values[':cat'] = $_GET['c'];
}
// etc.
}
return $this->index($sql, $values);
}
Filtering inputs
Escaping values is not needed, for sql injection protection that is, when using parameterized queries. However, sanitizing your inputs is always a correct idea. Sanitize it outside of the function, then pass it as an argument to the "search" function, decoupling the function from the superglobal $_GET. Defining the arguments for filtering is out of the rather large scope of this post, consult the documentation.
// global code
// create $pdo normally
$instance = new myclass($pdo);
$inputs = filter_input_array(INPUT_GET, $args);
$results = $instance->gen_search_clause($inputs);

MySQLi prepared statement executes twice on insert [duplicate]

This question already has answers here:
Insert query on page load, inserts twice?
(3 answers)
Closed 2 years ago.
I usually don't post detailed versions of my code; however, in this case it may be necessary to figure out the problem. I have a class method that I cannot stop from executing twice. Is there any particular information I am missing on MySQLi prepared statements? I have read similar questions that asks the same to no avail.
I previously asked a question about using eval for dynamic queries in prepared statements as far as it's convention and best practices. I was told to use call_user_func_array() and it worked perfectly; however, I failed to notice that the statement executed twice each time, even with the old eval() code. So I put together a snippet of my ACTUAL code which should pretty much explain itself through my comments
function insert($table, $query)
{
/**
*
* This code assumes you have a MySQLi connection stored in variable $db
* USAGE: insert(table, array('field' => 'value');
*
**/
// Sets the beginning of the strings for the prepared statements
$fields = $values = "(";
$types = "";
$params = array();
foreach($query as $key => $val)
{
// array keys = fields, and array values = values;
$fields.= $key;
// concatenate the question marks for statement
$values.= "?";
// concatenate the type chars
$types.= is_string($val) ? "s" : (is_int($val) ? "i" : (is_double($val) ? "d" : "b"));
// pass variables to array params by reference for call_user_func_array();
$params[] = &$query[$key];
if($val == end($query))
{
$fields .= ")";
$values .= ")";
array_unshift($params, $types);
}
else
{
$fields .= ", ";
$values .= ", ";
}
}
$str = "INSERT INTO {$table} {$fields} VALUES {$values}";
if($stmt = $db->prepare($str))
{
call_user_func_array(array($stmt, 'bind_param'), $params);
/**
*
* This is where I am pulling my hair out of my head and being 3
* nothces away from banging my own head into the screen and
* being without a computer at all.
*
* I have tried everything I can think of. I gotta be missing
* something
*
* IT JUST KEEPS SENDING 2 ROWS DANG IT!
*
**/
/////////////////////
$stmt->execute();//// <---Help is needed here
/////////////////////
//-- Close connection;
$stmt->close();
}
else
{
//-- Send a nice readable error msg
die("<center><h3>FAULTY QUERY STRING</h3><h4>Please check query string</h4><p>{$str}</p>");
}
}
Changed code format from OOP to regular function for testing without having to create a class.
old question, but people might still stumble upon it, I did.
I think the mysqli_query function is mainly for retrieving data, I had the same problem, and fixed it by using mysqli_real_query on insert and update queries.

How can I write a PHP function that takes an arbitrary number of parameters?

I am trying to find a way to create a function in PHP that will wrap a SQL query given in the parameter so that I can prevent SQL Injection in the function that can then be called many times throughout my application. Rather than repeating the same statements for each and every query.
For example say I have the following PHP code that prepares and executes a query to prevent SQL injection:
$name = "$_POST['name']";
$stmt = $db->prepare('SELECT * FROM test_table WHERE test_name = ?');
$stmt->execute(array($name));
For each query my application will need to make these statements will need to be repeated. I want a way to prevent having to do this each time, rather I would simply want to call a function each time and pass in the query.
How would I wrap this in a function that can then be called whenever I need to make a query in my application, given that I do not know in advance the amount of parameters that would need to be parameterized. The above query has one parameterized query, but each query may have a different amount.
Note:
I am using PDO statements
Something like this:
public function query($query)
{
// statements here
}
Where the query is passed in as a parameter.
Does anyone know how I can achieve this?
Currently, I am using something like this that might work for you.
Example:
function superQuery($query, $params, $type = null) {
$pdo = new pdo(...);
$stmt = $pdo->prepare($query);
$stmt->execute($params);
if ($type === "select") {
$result = $stmt->fetchAll();
return $result;
} else {
return $stmt;
}
$query = "SELECT row FROM column WHERE row1 = ? AND row2 = ?";
$params = [$row1, $row2];
$type = "select";
$row = selectQuery($query, $params, $type);
// returns multidimensional array or true/false depending if argument is used //
There's lots of ways you can do it. You could also pass a count argument if you wanted to return a count instead of a result set. But hopefully this points you in the right direction and gives you some ideas.

How to use php array in a Prepared Statement for SQL IN Operator using SQLi? [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 8 years ago.
This is my code:
if(isset($_POST['abc']))
{
$things['abc'] = mysqli_real_escape_string($connect, implode("','", $_POST['abc']));
$result = mysqli_query($connect, "SELECT * FROM this_list WHERE abc_column IN ('{$things['abc']}')");
if (!$result)
{
echo "Error fetching results: " . mysqli_error();
}
else
{
while ($row = mysqli_fetch_array($result))
{
$abc[] = $row['description'];
}
}
}
The above code uses mysqli_real_escape_string(), and $things is an array with checkbox values that is received via POST. This array contains the list of strings separated by comma that I am using in the query.
When I was searching on the net, I noticed that some people say mysqli_real_escape_string() may prevent sql injection, I was thinking maybe prepared statement for checkbox values might be more safer against sql injection.
I have used prepared statement with separate parameters to prevent sql injection. But I am stuck on this one and I dont know how to change the above code to a prepare() statement since it uses an array $things['abc']. I tried searching and everytime I search array in prepared statement, I am getting info on Java, etc.. Can someone enlighten me on how I can do this with php please?
EDIT:
After the help from onetrickpony code below, this is what I have now:
if(isset($_POST['abc']))
{
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT col1 FROM abc_table WHERE col2 IN (%s)", $ph);
$stmt = mysqli_prepare($connect, $query);
// bind variables
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
array_unshift($params, $stmt, str_repeat('s', count($_POST['abc']))); // s = string type
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stmt);
// Get the data result from the query.
mysqli_stmt_bind_result($stmt, $col1);
/* fetch values and store them to each variables */
while (mysqli_stmt_fetch($stmt)) {
$name[] = $col1;
echo $name;
}
//loop to echo and see whats stored in the array above
foreach($name as $v) {
echo $v;
}
// Close the prepared statement.
$stmt->close();
}
In the above code, the sqli method for prepare statement seems to work which is great. However, when I use the mysqli_stmt_bind_result(), the $name[] array inside the while loop only seems to print the last row.
UPDATE:
onetrickpony's code with the mysqli method for using php array in a Prepared Statement worked fine and it was a very good approach he had suggested. However, I have been having nightmare with the second half of the code which is trying to get the fetched array results to work. After trying for more than a day, I have given up on that and I have made the switch to PDO. Again onetrickpony's advise below was totally worth it. Making the switch to PDO made the code so much easier and simpler and couldnt believe it.
Try this:
// build placeholder string (?,?...)
$ph = rtrim(str_repeat('?,', count($_POST['abc'])), ',');
$query = sprintf("SELECT * FROM this_list WHERE abc_column IN (%s)", $ph);
$stm = mysqli_prepare($connect, $query);
// bind variables (see my notes below)
$params = array();
foreach($_POST['abc'] as $v)
$params[] = &$v;
// s = string type
array_unshift($params, $stm, str_repeat('s', count($_POST['abc'])));
call_user_func_array('mysqli_stmt_bind_param', $params);
mysqli_stmt_execute($stm);
It appears that mysqli_stmt_bind_param cannot be called multiple times to bind multiple variables. And even worse, it requires referenced variables. I'd recommend you switch to PDO, just because of these limitations that force you to write ugly code :)

convert mysql to pdo

So i have a function thats supposed to handle all data execute operations: sql
function loadResult($sql)
{
$this->connect();
$sth = mysql_query($sql);
$rows = array();
while($r = mysql_fetch_object($sth)) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
I want to convert it to pdo and this is what i have so far: pdo
function loadResult($sql)
{
$this->connect();
$sth = $this->con->prepare($sql);
//execute bind values here
$sth->execute();
$rows = array();
while ( $r = $sth->fetch(PDO::FETCH_OBJ) ) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
Here is an example of a function on how am using it to view data from the database:
function viewtodolist()
{
$db=$this->getDbo(); //connect to database
$sql="SELECT * FROM mcms_todolist_tasks";
//maybe the bind values are pushed into an array and sent to the function below together with the sql statement
$rows=$db->loadResult($sql);
foreach($rows as $row){echo $row->title; //echo some data here }
}
I have just pulled out the important snippets so some variables and methods are from other php classes. Somehow, the mysql query works fine, but the PDO query is giving me headaches on how to include bindValue paremeters most probably in the viewtodolist() function to make it reusable. Any suggestions/recommendations are welcome.
Since your existing function accepts a fully-formed SQL string, with no placeholders, you don't need to use prepare + bind. Your code as written should work fine, or you could use PDO::query() to execute the SQL in one step.
If you want to use parameterised queries, then your loadResult function is going to have to change a bit, as is the way you write your SQL. The example SQL you give doesn't actually have anything in that could be turned into a parameter (column names and table names can't be parameters as discussed here), but I'll use an imaginary variation:
// Get the todo tasks for a particular user; the actual user ID is a parameter of the SQL
$sql = "SELECT * FROM mcms_todolist_tasks WHERE user_id = :current_user_id";
// Execute that SQL, with the :current_user_id parameter pulled from user input
$rows = $db->loadResult($sql, array(':current_user_id' => $_GET['user']));
This is a nice secure way of putting the user input into the query, as MySQL knows which parts are parameters and which are part of the SQL itself, and the SQL part has no variables that anyone can interfere with.
The simplest way of making this work with your existing loadResult function would be something like this:
// Function now takes an optional second argument
// if not passed, it will default to an empty array, so existing code won't cause errors
function loadResult($sql, $params=array())
{
$this->connect();
$sth = $this->con->prepare($sql);
// pass the parameters straight to the execute call
$sth->execute($params);
// rest of function remains the same...
There are cleverer things you can do with parameterised queries - e.g. binding variables to output parameters, preparing a query once and executing it multiple times with different parameters - but those will require more changes to the way your calling code works.

Categories