creating a flexible update query with Php and pdo - problems with bindparam - php

I'm updating my mysql functions to use PDO. I've got the hang of most of it but struggling with an update function to update multiple fields in a records.
The function is in a class and I'm trying to keep it flexible to reuse with other tables etc.
Here's my function so far:
public function dbUpdateRecord($table, $values, $where)
{
$this->conn();
$fieldNames = array_keys($values);
var_dump($fieldNames);
$set="";
foreach ($fieldNames as $field) {
$set .= " $field = :$field,";
}
//strip last comma
$set = substr($set, 0, strlen($set) - 1);
$wherefields = array_keys($where);
$whereCondition="";
foreach ($wherefields as $field) {
$whereCondition .= " $field = :$field AND";
}
//strip last AND
$whereCondition = substr($whereCondition, 0, strlen($whereCondition) - 3);
$sql = "UPDATE $table SET $set WHERE $whereCondition";
var_dump($sql);
$stmt = $this->db->prepare($sql);
foreach ($values as $field => $value) {
$stmt->bindParam(':$field', $value);
}
foreach ($where as $field => $value) {
$stmt->bindParam(':$field', $value);
}
return $stmt->execute();
}
The problem is all the fields in the record are being updated by the id of the record which is contained in the $where variable.
$values contains an array of (fieldname=>value).
I think the problem lies around the bindparam and trying to make the fieldnames/placeholders dynamic
I thought I needed to use bindparam as best practice - is this correct or can I just go to execute()?
ANy help appreciated

You are lifting this log from the wrong end.
Your approach is potentially insecure yet inflexible at the same time.
What if you need a JOIN based update? What if you need OR in the WHERE (or IN)?
What you really need is a conventional query where only SET statement values have to be generated.
So, you need a helper function to produce such a statement out of data array, returning both correctly formatted SET statement and array with variables to be bound:
$fields = array("name","email");
$sql = "UPDATE users SET ".pdoSet($fields,$values,$data)." WHERE id = :id"
// now we have $values array to be passed into query
$stmt = $dbh->prepare();
$values["id"] = $_POST['id'];
$stmt->execute($values);
With this code you'll be able to make updates for the arbitrary query. And make it safe.
As a further step you will need to start using type-hinted placeholders, to make whole code like this:
$db->query("UPDATE ?n SET ?u WHERE id IN(?a)",$table,$data,$ids);
Getting back to your problem, ONe is right - you need to use bindValue instead of bindParam (as it mentioned in the tag wiki)

I believe the problem is that you are using a foreach to bind the params to the query. Why is this a problem? Because when you bind a variable, you bind a reference to that variable, so if that variable changes, the value in the query will change too. Since you are using a foreach loop, the value for all the parameters will be the latest value that the variable $value referenced to.
You can read more about this foreach behavior here and here. So basically, you have 2 options:
Use a reference to the actual value, instead of using a reference to $value (which can change its value in the next iteration)
Use an auxiliar variable that references another memory position that won't change during the loop

I came here because I was having the same problems, and YCS's solution was what I needed. For anyone else in this situation, here's the helper function I ended up using:
function commit_table($record_id, $changed_values)
{
$db = open_database();
$query = 'UPDATE table SET ';
$query_arguments = array();
$is_first = TRUE;
foreach(array_keys($changed_values) as $key)
{
if($is_first)
{
$is_first = FALSE;
}
else
{
$query .= ', ';
}
$value_var = ':' . $key;
$query .= $key;
$query .= ' = ';
$query .= $value_var;
$query_arguments[$value_var] = $changed_values[$key];
}
$query .= ' WHERE record_id = :record_id';
$query_arguments[':record_id'] = $record_id;
$stmt = $db->prepare($query);
$stmt->execute($query_arguments);
close_database($db);
}

Related

How to execute prepare statement using if statement in PHP PDO?

I am fetching some data from MySQL database using PHP PDO prepared statement. I have to use if statement inside the execution of the prepared statement. See my codes below for better understanding
$query = "SELECT * FROM my_table WHERE 1=1";
if(isset($_GET['one'])){
$query .= " AND one = :one";
}
if(isset($_GET['two'])){
$query .= " AND two = :two";
}
if(isset($_GET['three'])){
$query .= " AND three = :three";
}
$result = $db->prepare($query);
$result->execute([
/* ------------------------------------
How to declare the above parameters here
as it will show error if any of the if statement is not true?
----------------------------------------*/
]);
I want to know how to declare the prepared array parameter using if statement inside the $result->execute(......]) block?
You need to create an empty $params array, and inside each if block you can push the appropriate value to it. For example:
if(isset($_GET['one'])){
$query .= " AND one = :one";
$params[':one'] = $_GET['one'];
}
Then you can simply do
$result->execute($params);
Note that you can based on what you've written, you could simplify your code with an outer foreach on a list of parameter names e.g.
$names= array('one', 'two', 'three');
$params = array();
foreach ($names as $name) {
if (isset($_GET[$name])) {
$query .= " AND $name = :$name";
$params[":$name"] = $_GET[$name];
}
}
$result->execute($params);

PHP form sending multiple inputs as SQL queries

I'm trying to create a PHP script that takes based on which input fields are filled in out of the total, uses those fields as SQL parameter to create a SQL query. But if none of the fields are filled out, then query would just retrieve all data.
Example: if fields "Name" and "City" are entered, then the query will have to concatenate "AND name = X_INPUT AND city = Y_INPUT" at the end.
I tried to instantiate $POST variables for the sake of testing but returns nothing. I'm not even sure if this is the best way to tackle the problem but any help would be greatly appreciated!
$_POST['name']="Linda";
$_POST['city']="";
$query = "select * from customers where gender = 'F'";
if (empty($_POST)) {
$query = "select * from customers where gender ='F'";
}
else{
foreach($_POST as $key => $v) {
if (!empty($_POST[$key])) {
$cond = " and ".$key."='".$v."'";
$query = $query.$cond;
The code:
$query = $query.$cond;
every appends unnecessary query to the SQL. Get all your conditions in an array and simple concatenate them as a string. Everything should work this way.
$conditions = array();
foreach($POST as $key => $v) {
if (!empty($_POST[$key])) {
$conditions = " $key = '$v'";
}
}
if (! empty($conditions)) {
$sql .= implode(' AND ', $conditions);
}
You have a typo in your foreach loop, Use this
$_POST
instead of
$POST

Php function to run mysql queries

I am trying to create functions to run mysql queries
How would I do things like insert queries. I was thinking
function insert_query ($table,$cols,$values)
{
$sql="insert into $table ($cols) values ($values) "; ...etc
}
With the rest of the query code in the function. But how would I add multiple columns and values?
Should I make $cols and $values An array inside the function?
This is a function of my Database Class.
public function insert($table,$values){
$fieldNames = "";
$fieldValues = "";
foreach($values as $key => $value){
$fieldNames .= "$key,";
$fieldValues .= "$value,";
}
$fieldNames = substr($fieldNames,0,-1);
$fieldValues = substr($fieldValues,0,-1);
$sql = "INSERT INTO $table($fieldNames) VALUES ($fieldValues)";
$this->newConnection();
$result = $this->mysqli->query($sql);
$this->closeConnection();
return $result;
}
Here is what I'm using. Pass field name and Value as Array key and value. $lsQry is an array of field name & value pair
function insert_record($table,$lsQry)
{
$fields_str = implode(',',array_keys($lsQry));
$data_str = implode("','",$lsQry);
$data_str = "'" . implode("','", $lsQry) . "'";
$lsQry = "INSERT INTO $table($fields_str) VALUES($data_str)";
$rs = mysql_query($lsQry);
if(isset($rs))
return true;
else
return false;
}
Please Note
For this function, do consider that function is getting an array of fields name and value pair. It is assumed that htmlentities() and addslashes() or any escaping functions are already applied while creating array from post/get values.
Easy, just us arrays
function insert_query ($table,$cols,$values){
$sql="insert into $table (".implode(",", $cols).") values (".implode("','", $values)."'') ";
}
insert_query('exampleTable', array('column_1', 'column_2', 'column_3'), array('a', 123, 'c') );
The implode for the values requires a small sidenote:
Strings always required being wrapped in quotes. Therefor I made the implode with single qoutes. The downside to this is that integets (like 123 in the example) also get wrapped.
This is not a big problem, but if you want you could replace the implode with a foreach that uses is_numeric to check wether it should be wrapped in quotes.
IMPORTANT SECURITY NOTE:
In this example I havent used proper seurity, like escape_string(), this has to be added! I've not added thos to keep the examples smaller
Another approach could be key/value-usage of an array:
function insert_query ($table,$data){
$cols = array_keys($data);
$values = array_values($data);
$sql = "insert into $table (".implode(",", $cols).") values (".implode("','", $values)."'') ";
}
$info = array('column_1'=>'a', 'column_2'=>123, 'column_3'=>'c');
$info['example'] = 'Easy method to add more key/values';
insert_query('tableName', $info);
In this case you can use functions similar to codeigniter functions.
Use arrays to store table name and columns or values
For example:
$data = array('hid' => $hcsdate,'start_date' => $sdate, 'end_date' => $edate, 'title' =>$title);
Here $data holds the column name and corresponding values.
And pass this $data to another functions for insert, update etc..

Solving inequality issue between bound variables and tokens, when number of bound variables is dynamic. (PDO/PHP MySQL)

Context:
I am attempting to update a record with a set of values in a MySQL database using the usual UPDATE syntax, via PDO method of binding tokens to data.
The Problem:
The number of fields to be updated is dynamic, and is adjusted depending on what data has been POSTed to the page. Using an if(isset($_POST[])) to filter out updates to fields which may already have data in, but I don't want to be overwritten by empty data.
This means that the number of tokens I am creating varies, but my syntax for binding data to the tokens is static, and I'm not sure how to adjust it.
This of course results in: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
My Code:
Please note that $data is an array of all the column names in my database. It is also the set of name attributes of the variables that may be posted, such that $_POST[x] should be updated to the column x in a given record, and x is a member of $data.
$sql_b = "UPDATE `temp_data` SET ";
foreach($data as $value)
{
if(isset($_POST[$value]))
$sql_b .="$value = :$value, ";
}
$sql_b = rtrim($sql_b,', ');
$sql_b .=" WHERE UID = '$uid'";
try
{
$pdo = new PDO('mysql:host=localhost; dbname=db01', $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare($sql_b);
foreach($data as $value)
{
$stmt->bindParam(":$value", $_POST[$value]);
}
$stmt->bindParam(":sv_215_hidden", $_POST[sv_215_hidden]);
$stmt->bindParam(":sv_216_hidden", $_POST[sv_216_hidden]);
$stmt->execute();
# Affected Rows?
echo $stmt->rowCount(); // 1
} catch(PDOException $e) {
echo 'Error: ' . $e->getMessage();
}
To highlight the problem areas of this:
This code:
foreach($data as $value)
{
if(isset($_POST[$value]))
$sql_b .="$value = :$value, ";
}
Creates a subset of possible UPDATE requests
Where as this code:
foreach($data as $value)
{
$stmt->bindParam(":$value", $_POST[$value]);
}
Always creates a full set of bindings between all possible tokens.
Central Question:
How can I adjust the number of bindings to match only the fields being updated?
You're already there; you're checking to see what fields are set when you generate the SQL statement:
foreach($data as $value)
{
if(isset($_POST[$value]))
$sql_b .="$value = :$value, ";
}
So you can use the exact same logic when you're calling bindParam():
foreach($data as $value)
{
if(isset($_POST[$value]))
$stmt->bindParam(":$value", $_POST[$value]);
}
I think you need to do a couple of extra steps:
$keys = array('foo', 'bar', 'baz', 'foobar');
$updates = array();
$bound = array();
foreach($keys as $key){
if (isset($_POST[$key])){
$updates[] = $key . '= :' . $key;
$bound[':' . $key] = $_POST[$key];
}
}
$sql = 'UPDATE table SET ' . implode(',', $updates) . ' WHERE etc'; //add your where clause
$stmt = $pdo->prepare($sql);
$stmt->execute($bound);
In other words, you can pass an array of bound parameters to PDOStatement::execute rather than binding them individually beforehand; and doing things as above makes sure that the bound parameters match what's in your SQL.

Build SQL Select a better way ? (Oracle)

i have the following code part in one of my classes:
$l = new Location();
$result = $l->getLocIdsbyCity($city); // returns csv
$ids = explode(',', $result);
$where = 'LOC_ID = ' . $ids[0];
unset($ids[0]);
foreach ($ids as $id) {
$where .= ' OR LOC_ID = ' . $id;
}
$select->where($where);
Is there an more "elegant" way to build the select stmt? I need all records with one of the provided ids..
Assuming your csv is injection safe (contains trusted values and no user-provided input):
$l = new Location();
$result = $l->getLocIdsbyCity($city); // returns csv
$where = "LOC_ID IN ($result)";
$select->where($where);
If it's not, you should explode it, mysql_real_escape_string each value and implode back.
You can use the in operator to form a condition like:
where LOC_ID in (1,2,3,4,5)
If you are sure that $result can't contain anything harmful, you should be able to use it directly without having to split it and loop.

Categories