Dynamic prepared statement, PHP - php

I've checked almost all questions that produce the same error but all of these questions bind parameters in some wrong way. Perhaps and most probably I too am binding params incorrectly though my case is different because I've dynamic query.
I am creating query dynamically from input which is being created perfectly. But problem comes from $stmt->bind_param statement within foreach loop. Here is my Code snippet that is erronous:
$query = "UPDATE users SET";
foreach($updationFields as $field => $value){
if($value != "-"){
$query = $query. " " . $field . " = :".$field.",";
}
}
$query = rtrim($query, ",");
$query = $query . " WHERE UserId = :UserId";
$stmt = $this->conn->prepare($query);
foreach($updationFields as $field => $value){
echo $field;
if($value != "-"){
$input = ":".$field;
$stmt->bind_param($input, $value); // This line produces error
}
}
$stmt->bind_param(":UserId", $userId);
$stmt->execute();
Here is produced dynamic "string query" for one field:
UPDATE users SET fullName = :fullName WHERE UserId = :UserId
Error says: Fatal error: Call to a member function bind_param() on a non-object in
Any Idea what i am doing wrong?

As pointed out by #Fred-ii- and #commorrissey :Placeholder is supported by PDO not mysqli so so I had to:
Replace :Placeholders with ?
Call bind_param with call_user_func_array feeding dynamic references as expected by mysqli_stmt.
Here is the code that creates dynamic binding:
$params = array();//
$params[] = $type;
$i=0;
foreach($updationFields as $field => $value){
if($value != "-"){
$bind_name = 'bind' . $i;
$$bind_name = $value;
$params[] = &$$bind_name;
$i++;
}
}
$bind_name = 'bind' . $i;
$$bind_name = $userId;
$params[] = &$$bind_name;
$return = call_user_func_array(array($stmt,'bind_param'), $params);

Related

Prepared Statement when not knowing how many parameters

I'm trying to convert a complex php file I made a year ago over to prepared statements. Parameters will be passed in like so:
file.php?name=John&age=20
However there could be many more parameters that are potentially used. job, address, phone number, etc etc. This is where I tend to get confused with prepared statements.
For example:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
}
$stmt = $db->prepare($query);
$stmt->bind_param('sis', $_REQUEST['name'], $_REQUEST['age'], $_REQUEST['address']);
$stmt->execute();
The issue here is bind_param because I don't know how many parameters could potentially be available.
How would I go about this in a logical manner?
A very good question. And the solution is very simple.
What you need is called argument unpacking operator. It will allow you to use an array of arbitrary length with bind_param. All you need is to collect accepted parameters into array, then create the types string dynamically and finally use the aforementioned operator:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
$params[] = $_REQUEST['name'];
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
$params[] = $_REQUEST['age'];
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
$params[] = $_REQUEST['address'];
}
if ($params)
$stmt = $db->prepare($query);
$types = str_repeat("s", count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $db->query($query);
}
Well, the process is going to be quite similar to how you build up your $query variable - i.e. you add to the parameters list one at a time.
Consider that bind_param requires two things:
First is a list of the data types as a simple string. So you can just have a string variable which you add to for each parameter, and then pass that to bind_param at the end.
Second is a list of parameters. This is trickier, because you can't just supply an array to bind_param. However, you can create an array of your own, and then use call_user_func_array to convert that to a flat list.
Here's one I wrote earlier (and works nicely). Note that it attempts to detect the parameter types and create a suitable string, but you could just build up a string manually in your if statements if you prefer:
$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();
if (isset($_REQUEST['name'])) {
$query .= " and name = ?";
$params[] = $_REQUEST['name'];
}
if (isset($_REQUEST['age'])) {
$query .= " and age = ?";
$params[] = $_REQUEST['age'];
}
if (isset($_REQUEST['address'])) {
$query .= " and address = ?";
$params[] = $_REQUEST['address'];
}
$stmt = $db->prepare($query);
if (!is_null($params))
{
$paramTypes = "";
foreach ($params as $param)
{
$paramTypes .= mysqliContentType($param);
}
$bindParamArgs = array_merge(array($paramTypes), $params);
//call bind_param using an unpredictable number of parameters
call_user_func_array(array(&$stmt, 'bind_param'), getRefValues($bindParamArgs));
}
$stmt->execute();
function mysqliContentType($value)
{
if(is_string($value)) $type = 's';
elseif(is_float($value)) $type = 'd';
elseif(is_int($value)) $type = 'i';
elseif($value == null) $type = 's'; //for nulls, just have to assume a string. hopefully this doesn't mess anything up.
else throw new Exception("type of '".$value."' is not string, int or float");
return $type;
}
function getRefValues($arr)
{
$refs = array();
foreach($arr as $key => $value)
{
$refs[$key] = &$arr[$key];
}
return $refs;
}

php mysqli prepared statements select

Trying to get a function working to create simple CRUD "Select" with multiple parameters to any table. I think I got the hardest part, but couldn't fetch the data right now. Maybe I'm doing something wrong I can't figure out.
My prepared statement function:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
if($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
} else {
$error = $mysqli->errno . ' ' . $mysqli->error;
error_log($error);
}
}
The query creator:
function create_select_query($table, $condition = "", $sort = "", $order = " ASC ", $clause = ""){
$table = escape_mysql_identifier($table);
$query = "SELECT * FROM ".$table;
if(!empty($condition)){
$query .= create_select_query_where($condition,$clause);
}
if(!empty($sort)){
$query .= " ORDER BY ".$sort." $order";
}
return $query;
}
The helper function to create the WHERE clause:
function create_select_query_where($condition,$clause){
$query = " WHERE ";
if(is_array($condition)){
$pair = array();
$size = count($condition);
$i = 0;
if($size > 1){
foreach($condition as $field => $val){
$i++;
if($size-1 == $i){
$query .= $val." = ? ".$clause. " ";
}else{
$query .= $val." = ? ";
}
}
}else{
foreach($condition as $field => $val){
$query .= $val." = ? ";
}
}
}else if(is_string($condition)){
$query .= $condition;
}else{
$query = "";
}
return $query;
}
The select function itself:
function crud_select($conn, $table, $args, $sort, $order, $clause){
$sql = create_select_query($table, array_keys($args),$sort, $order, $clause);
print_r($sql);
if($stmt = prepared_query($conn, $sql, array_values($args))){
return $stmt;
}else{
$errors [] = "Something weird happened...";
}
}
When I create the query, it seems to be OK but can't fetch the data. If I create an array with only one argument the query translates into:
SELECT * FROM `teste_table` WHERE id = ?
If I create with multiple parameters, it turns like this:
SELECT * FROM `teste_table` WHERE id = ? AND username = ?
So, how can I properly fetch the data from the select. This should be used for multiple purposes, so I could get more than one result, so the best way would be fetch data as array I guess.
I guess I'm close, but can't figure it out. Thanks
I told you to limit your select function to a simple primary key lookup. And now you opened a can of worms. As a result you are getting entangled implementation code and unreadable application code.
$table, $args, $sort, $order, $clause
What all these variables are for? How you're going to call this function - a list of gibberish SQL stubs in a random order instead of plain and simple SQL string? And how to designate a list of columns to select? How to use JOINS? SQL functions? Aliases? Why can't you just write a single SQL statement right away? You already have a function for selects, though without this barbaric error reporting code you added to it:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
Just stick to it and it will serve you all right.
$sql = "SELECT * FROM `teste_table` WHERE id = ? AND username = ?";
$stmt = prepared_query($mysqli, $sql, [$id, $name]);
$row = $stmt->get_result()->fetch_assoc();
The only specific select function could be, again, a simple primary key lookup:
function crud_find($conn, $table, $id)
{
$table = escape_mysql_identifier($table);
$sql = "SELECT * FROM $table WHERE id=?";
$stmt = prepared_query($conn, $sql, [$id], "i");
return $stmt->get_result()->fetch_assoc();
}
And for the everything else just use a generic function with native SQL.

php function to mysqli update

Trying to create a function that would be used to update any row of any table, but I'm getting trouble getting into it.
Data sent in array where the array index is the field name in table and the value is the new value for that index.
For examplpe:
$args["name"] = "NewName";
$args["city"] = "NewCity";
$args["id"] = 4; // row ID to update
What I got:
function create_update_query($table, $keys){
$keys = array_map('escape_mysql_identifier', $keys);
$table = escape_mysql_identifier($table);
$updates = "";
$count = 0;
foreach($keys as $index => $value){
if($index != "id"){
$count++;
if($count == count($keys)-1){
$updates = $updates . "$index = ?";
}else{
$updates = $updates . "$index = ?,";
}
}
}
return "UPDATE $table SET $updates WHERE id = ? LIMIT 1";
}
After that, I have the function to really do the query:
function crud_update($conn, $table, $data){
$sql = create_update_query($table, array_keys($data));
if(prepared_query($conn, $sql, array_values($data))){
$errors [] = "OK";
}else{
$errors [] = "Something weird happened...";
}
}
The function that makes the prepared statement itself:
function prepared_query($mysqli, $sql, $params, $types = ""){
$types = $types ?: str_repeat("s", count($params));
if($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
} else {
$error = $mysqli->errno . ' ' . $mysqli->error;
echo "<br/>".$error;
}
}
When trying to submit the data with the following criteria:
$args['name'] = "Novo Nome";
$args['field'] = "New Field";
$args['numaro'] = 10101010;
$args['id'] = 4;
//create_update_query("teste_table", $args);
crud_update($link, "teste_table", $args);
Have an error:
1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1 = ?,2 = ?,3 = ? WHERE id = ? LIMIT 1' at line 1
But if I echo the query created by create_update_query it seems ok:
UPDATE `teste_table` SET name = ?,field = ?,numaro = ? WHERE id = ? LIMIT 1
Any help would be appreciated.
Thanks.
The problem is that as you pass the keys to create_update_query() as
create_update_query($table, array_keys($data));
Using array_keys() will just take the key names, so the $keys parameter is just a list of the field names as something like ...
Array(
0=> 'name',
1 =>'field',
2 =>'numaro'
)
You then extract the data using
foreach($keys as $index => $value){
and build your SQL with
$updates = $updates . "$index = ?";
so at this point, the indexes are the numeric value, so change these lines to...
$updates = $updates . "$value = ?";
which is the name of the field.
With the various other changes, I would suggest the code should be...
foreach($keys as $value){
if($value != "id"){
$updates = $updates . "$index = ?,";
}
}
$updates = rtrim($updates, ",");
return "UPDATE $table SET $updates WHERE id = ? LIMIT 1";

mysqli_prepare query returning false

I am attempting to bind params to a sql statement using call_user_func_array as describe on Dynamically Bind Params in Prepared Statements with MySQLi; however, my mysqli_prepare keeps returning false.
Here is my data function that is called to store data:
function storeData($form_data, $table_name, $cxn){
if(!is_array($form_data)){
return false;
exit();
}
$types = str_repeat("s", count($form_data));
$params = array();
$params[] = &$types;
$keys = array_keys($form_data);
$values = array_values($form_data);
for ($i = 0; $i < count($values); $i++) {
$params[] = &$values[$i];
}
$sql = "INSERT INTO $table_name (" . implode(',', $keys) . ") VALUES (" .
implode(',', array_fill(0, count($values), '?')) . ")
ON DUPLICATE KEY UPDATE ";
$updates = implode(',', array_map(function($col) {
return "$col = VALUES($col)";
}, $keys));
$sql .= $updates;
if($stmt = mysqli_prepare($cxn, $sql)){
call_user_func_array(array($stmt, 'bind_param'), $params);
return mysqli_stmt_execute($stmt);
}
Here is my $sql string at time of prepare:
$sql"INSERT INTO interest (Baseball,Basketball,Camping,Canoeing,Cycling,Football,Gaming,Golf,Hiking,Parks,Photography,Runway,Skydiving,Soccer,Username) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE Baseball = VALUES(Baseball),Basketball = VALUES(Basketball),Camping = VALUES(Camping),Canoeing = VALUES(Canoeing),Cycling = VALUES(Cycling),Football = VALUES(Football),Gaming = VALUES(Gaming),Golf = VALUES(Golf),Hiking = VALUES(Hiking),Parks = VALUES(Parks),Photography = VALUES(Photography),Runway = VALUES(Runway),Skydiving = VALUES(Skydiving),Soccer = VALUES(Soccer),Username = VALUES(Username)"
Here is my $params and $key outputs:
$keysarray[15]
$keys[0]"Baseball"
$keys[1]"Basketball"
$keys[2]"Camping"
$keys[3]"Canoeing"
$keys[4]"Cycling"
$keys[5]"Football"
$keys[6]"Gaming"
$keys[7]"Golf"
$keys[8]"Hiking"
$keys[9]"Parks"
$keys[10]"Photography"
$keys[11]"Runway"
$keys[12]"Skydiving"
$keys[13]"Soccer"
$keys[14]"Username"
$paramsarray[16]
$params[0]"sssssssssssssss"
$params[1]"0"
$params[2]"0"
$params[3]"0"
$params[4]"0"
$params[5]"0"
$params[6]"0"
$params[7]"0"
$params[8]"0"
$params[9]"0"
$params[10]"0"
$params[11]"0"
$params[12]"0"
$params[13]"0"
$params[14]"0"
$params[15]"test0613"
$valuesarray[15]
$values[0]"0"
$values[1]"0"
$values[2]"0"
$values[3]"0"
$values[4]"0"
$values[5]"0"
$values[6]"0"
$values[7]"0"
$values[8]"0"
$values[9]"0"
$values[10]"0"
$values[11]"0"
$values[12]"0"
$values[13]"0"
$values[14]"test0613"
There error existed in a column i was attempting to map which did not exist. The error procedure was found here, which allowed me to produce fatal errors that noted a column did not exist in the table I was referencing.

building db query with a for loop

I've made a function to query the database. This function takes an array, the id of the user I want to update
and a query operation.
if the query operation is UPDATE
if you look at the code below, would this be a good coding practice or is this bad code?
public function query($column, $search_value, $query_operation = "SELECT"){
if(strtoupper($query_operation == "UPDATE")){
$query = "UPDATE users SET ";
if(is_array($column)){
$counter = 1;
foreach($column as $key => $value){
if($counter < count($column)){
$query .= $key . ' = ?, ';
}else{
$query .= $key . ' = ? ';
}
$counter++;
}
$query .= "WHERE id = ?";
$stmt = $this->database->prepare($query);
$counter = 1;
foreach($column as $key => &$value){
$stmt->bindParam($counter, $value);
$counter++;
}
$stmt->bindParam($counter, $search_value);
if($stmt->execute()){
$stmt = $this->database->prepare("SELECT* FROM
users WHERE id = ?");
$stmt->bindParam(1, $search_value, PDO::PARAM_INT);
$stmt->execute();
return $this->build_array($stmt);
}
}
}
}
would love to hear some feedback.
I would NOT mix SELECT and UPDATE in the same function.
The following update function uses arrays for column names and values $columnNames & $values using unnamed parameters.
function update($tableName,$columnNames,$values,$fieldName,$fieldValue){
$sql = "UPDATE `$tableName` SET ";
foreach($columnNames as $field){
$sql .= $field ." = ?,";
}
$sql = substr($sql, 0, -1);//remove trailing ,
$sql .= " WHERE `$fieldName` = ?";
return $sql;
}
As table and column names cannot be passed as parameters in PDO I have demonstrated whitelistng of table names.
$tables = array("client", "Table1", "Table2");// Array of allowed table names.
Also array_push()to add value for last parameter (WHERE) into $values array
Use
if (in_array($tableName, $tables)) {
$sql = update($tableName,$columnNames,$values,$fieldName,$fieldValue);
array_push($values,$fieldValue);
$STH = $DBH->prepare($sql);
$STH->execute($values);
}
You can use similar technique for SELECT

Categories