I'm preparing my own function due to hurry the updates automatically.
I have that code:
$allowededitablefields = array('mail');
$userid = $_GET['uid'];
$query = 'UPDATE users SET ';
foreach ($_POST as $key => $value) {
if(!in_array($key,$allowededitablefields)) {
unset($_POST[$key]);
}
else {
$query .= $key.' = :'.$key.',';
}
}
$query = substr($query, 0, -1);
$query .= ' WHERE id='.$userid;
$statement = $this->_db->prepare($query);
foreach ($_POST as $key => $value) {
$statement->bindParam(':'.$key,$value);
}
$statement->execute();
If in $allowededitablefields array, I have only a value, it works properly, but if I push some values to the array, for example $allowededitablefields = array('mail','country',...); the fields in the table take the same values.
$value holds the value of the last iteration when the foreach loop ends.
change the bindParam to this.
$statement->bindParam(':'.$key,$_POST[$key]);
This should work, but your approach is fundamentally flawed. It undermines the whole purpose of prepared statements.
Related
Would parameterized code that uses concatenation in this way have a SQL injection vulnerability? I assume that it would, but I'm not certain what POST data would exploit it.
foreach ($_POST as $key => $value) {
$columns .= ($columns == "") ? "" : ", ";
$columns .= $key;
$holders .= ($holders == "") ? "" : ", ";
$holders .= ":".$value;
}
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt = $this->pdo->prepare($sql);
foreach($_POST as $key => $value) {
$field = ":".$key;
$stmt->bindValue($field, $value);
}
$stmt->execute();
You need an array to store all the columns of request table and check if the post key is present in the array.
PHP code:
$request_columns = array('column1','column2');// all the columns of request table
foreach ($_POST as $key => $value) {
if(in_array($key,$request_columns)){
$columns .= ($columns == "") ? "" : ", ";
$columns .= $key;
$holders .= ($holders == "") ? "" : ", ";
$holders .= ":".$key;
}
}
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt = $this->pdo->prepare($sql);
foreach($_POST as $key => $value) {
if(in_array($key,$request_columns)){
$field = ":".$key;
$stmt->bindValue($field, $value);
}
}
$stmt->execute();
#KetanYekale is correct that you need to filter $_POST for known column names.
Here's an alternative way of doing it, using some PHP builtin functions.
$request_columns = array('column1','column2');// all the columns of request table
# get a subset of $_POST, only those that have keys matching the known request columns
$post_only_columns = array_intersect_key(
$_POST,
array_flip($request_column)
);
# make sure columns are delimited like `name` in case they are SQL reserved words
$columns = implode(array_map(function ($col) { return "`$col`"; }, array_keys($post_only_columns), ', ';
# use ? positional holders, not named holders. it's easier in this case
$holders = implode(array_fill(1, count($post_only_columns), '?'), ', ');
$sql = "INSERT INTO request ($columns) VALUES ($holders)";
$stmt => $this->pdo->prepare($sql);
# no need to bindValue() or use a loop, just pass the values to execute()
$stmt->execute( array_values($post_only_columns) );
PHP has many Array functions that you can use in different scenarios to make your code faster and more concise. You can use these functions to avoid writing some types of foreach looping code.
I am trying to build a class that insert array of values into oracle database, I am also trying to make the class scalable (works with all forms of the application).
the reason am doing this is to reduce code repeating for the application am working on, because it has a lot of forms, and some of them has a lot of variables (50+).. therefore I need to make a class capable of inserting values into table, where the number of values is dynamic (depending on the form).
so far what I did is:
class ArrayQuery {
public $conn;
public $table;
public function __construct($conn){
$this->conn = $conn;
}
public function setTableName($table)
{
$this->table = $table;
}
public function InsertArray(array $args){
$keys = array_keys($args);
$values = array_values($args);
// need help here //
}
}
The construct function will get database connection, by calling the $conn object from the db_connection class (e.g $conn = new db_connection(); $query = new ArrayQuery($conn);)
setTableName is obviously for defining the desired table.
now the ArrayQuery, what I did in my form page, I named the keys of the array the same as the name of columns in table, for example array args['id']= $_post['id']. so basically array_key = column name. I did that in order to get columns name without manually setting them.
now I have 2 arrays: $keys (holds the name of columns), and $values (holds the data to be insert).
I cant figure out how to bind variables into keys? knowing that the number of variables is dynamic ?
any help will be much appreciated
UPDATE:
Here is how the $args array are set
$args = array(
'id' => $_POST['id'],
'firstname' => $_POST['firstname'],
'lastname' => $_POST['lastname'],
'email' => $_POsT['email'],
)
answer:
thanks to #calculon for putting me on the right track, with little modifications to work in my scenario, for future reference the answer to my case is:
$i=0; $col=''; $val='';
foreach ($args as $key => $value) {
if($i==0){
$col .= $key;
$val .= ':'.$key;
}
else{
$col .= ', '.$key;
$val .= ', :'.$key;
}
$i++;
}
$sql = 'INSERT INTO '.$table.' ('.$col.') VALUES ('.$val.') ';
$stmt = oci_parse($this->conn, $sql);
foreach ($args as $key => $value) {
oci_bind_by_name($stmt, $key, $args[$key]) ;
}
oci_execute($stmt);
hope it will be helpful, thanks again calculon.
Approximately such algorithm for inserting one row with an unknown number of columns I used in my project, for multiple rows should be added one more cycle
$i=0; $keys=''; $vals='';
foreach ($args as $key => $value) {
if($i==0){
$keys .= ''.$key;
$vals .= ':'.$key;
}
$keys .= ', '.$key;
$vals .= ', :'.$key;
$i++;
}
$sql = 'INSERT INTO '.$table.' ('.$keys.') VALUES ('.$vals.') ';
$stmt = oci_parse($conn, $sql);
foreach ($args as $key => $value) {
oci_bind_by_name($stmt, $key, $args[$key]) ;
}
oci_execute($stmt);
In my database I've fields like "status", which are reserved keywords. This code works fine for me (status is escaped by ``):
$sql = "UPDATE $table SET `status`='$status' WHERE `id`='123'";
But now I want to use prepared statements only! My Database.class:
class Database extends \PDO {
private $_sth; // statement
private $_sql;
public function update($tbl, $data, $where, $where_params = array()) {
// prepare update string and query
$update_str = $this->_prepare_update_string($data);
$this->_sql = "UPDATE $tbl SET $update_str WHERE $where";
$this->_sth = $this->prepare($this->_sql);
// bind values to update
foreach ($data as $k => $v) {
$this->_sth->bindValue(":{$k}", $v);
}
// bind values for the where-clause
foreach ($where_params as $k => $v) {
$this->_sth->bindValue(":{$k}", $v);
}
return $this->_sth->execute();
}
private function _prepare_update_string($data) {
$fields = "";
foreach ($data as $k => $v) {
$fields .= "`$k`=:{$k}, ";
}
return rtrim($fields, ", ");
}
}
Update example that won't work:
$DB = new Database();
$DB->update("tablename",
array("status" => "active"),
"`username`=:username AND `status`=:status",
array("username" => "foofoo", "status" => "waiting"));
I think, its because of the reserverd keyword "status". But I don't know how to escape it. I tried to escape the placeholder in _prepare_update_string($data) to:
bindValue("`:{$k}`", $v)
but no result.
I hope the solution is very simple and it's just a stuck overflow in my brain. ;-) Thanks in advance people!
When you construct the SQL string (prepare_update_string i think), as well as in both the foreach loops where you bind data, run an incrementing count and append it to the bind value. So ":status" become ":status1".
Something like:
$i = 1;
foreach ($data as $k => $v) {
$this->_sth->bindValue(":{$k.$i}", $v);
$i++;
}
This will solve the problem of any reserved keywords.
It also solves the problem (which I'm sure you'll encounter in the future) where you need to bind to the same placeholder more than once.
e.g. instead of the following, which throws an error due to two binds on the :status placeholder
SELECT * from table WHERE `status` = :status AND `otherfield` = :status
With an incrementing count, this becomes:
SELECT * from table WHERE `status` = :status1 AND `otherfield` = :status2
Enjoy.
I had a similar problem and solved by passing the parameter by reference
I have a varchar(3) field and a pointer was been passed instead of 'aaa' value
This works ($val by reference):
<?php
foreach ($params as $key => &$val) {
$sth->bindParam($key, $val);
}
?>
This will fail ($val by value, because bindParam needs &$variable):
<?php
foreach ($params as $key => $val) {
$sth->bindParam($key, $val);
}
?>
reference: Vili's comment at
https://www.php.net/manual/pt_BR/pdostatement.bindparam.php
In trying to create a simple PHP PDO update function that if the field is not found would insert it, I created this little snippet.
function updateorcreate($table,$name,$value){
global $sodb;
$pro = $sodb->prepare("UPDATE `$table` SET value = :value WHERE field = :name");
if(!$pro){
$pro = $sodb->prepare("INSERT INTO `$table` (field,value) VALUES (:name,:value)");
}
$pro->execute(array(':name'=>$name,':value'=>$value));
}
It does not detect though if the update function is going to work with if(!$pro); How would we make this one work.
You are assigning $pro to the prepare, not the execute statement.
Having said that, if you are using mysql you can use the insert... on duplicate key update syntax.
insert into $table (field, value) values (:name, :value) on duplicate key update value=:value2
You can't use the same bound param twice, but you can set two bound params to the same value.
Edit: This mysql syntax will only work where a key (primary or another unique) is present and would cause an insert to fail.
If it's mysql-only you could try INSERT INTO ... ON DUPLICATE KEY UPDATE
http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
You will first need to execute it.
Apart from that, this is a dodgy way of doing this. It would be better to start a transaction, do a SELECT and then determine what to do (INSERT or UPDATE). Just checking whether the UPDATE query succeeded doesn't suffice, it succeeds when no row is found too.
try,
PDO::exec()
returns 1 if inserted.
2 if the row has been updated.
for prepared statements,
PDOStatement::execute()
You can try,
PDOStement::rowCount()
The following are PHP PDO helper functions for INSERT and UPDATE
INSERT function:
function basicInsertQuery($tableName,$values = array()){
/*
//
USAGE INSERT FUNCTÄ°ON
$values = [
"column" => $value,
];
$result = basicInsertQuery("bulk_operations",$values);
*/
try {
global $pdo;
foreach ($values as $field => $v)
$vals[] = ':' . $field;
$ins = implode(',', $vals);
$fields = implode(',', array_keys($values));
$sql = "INSERT INTO $tableName ($fields) VALUES ($vals)";
$rows = $pdo->prepare($sql);
foreach ($values as $k => $vl)
{
$rows->bindValue(':' . $k, $l);
}
$result = $rows->execute();
return $result;
} catch (\Throwable $th) {
return $th;
}
}
UPDATE function:
function basicUpdateQuery($tableName, $values = array(), $where = array()) {
/*
*USAGE UPDATE FUNCTÄ°ON
$valueArr = [ column => "value", ];
$whereArr = [ column => "value", ];
$result = basicUpdateQuery("bulk_operations",$valueArr, $whereArr);
*/
try {
global $pdo;
//set value
foreach ($values as $field => $v)
$ins[] = $field. '= :' . $field;
$ins = implode(',', $ins);
//where value
foreach ($where as $fieldw => $vw)
$inswhere[] = $fieldw. '= :' . $fieldw;
$inswhere = implode(' && ', $inswhere);
$sql = "UPDATE $tableName SET $ins WHERE $inswhere";
$rows = $pdo->prepare($sql);
foreach ($values as $f => $v){
$rows->bindValue(':' . $f, $v);
}
foreach ($where as $k => $l){
$rows->bindValue(':' . $k, $l);
}
$result = $rows->execute();
return $result;
} catch (\Throwable $th) {
return $th;
}
}
how can I add $_POST['wmeet'] into the auto INSERT or should I do a separate UPDATE. I want all 6 $_POST['wmeet'] values to go into m_wmeet in format "ce1 sf1 sm1" a single space in between each value
if (is_array($_POST['add']))
foreach ($_POST['add'] as $key => $value)
$_POST['add'][$key] = mysql_real_escape_string(stripslashes($value));
if (is_array($_POST['wmeet']))
foreach ($_POST['wmeet'] as $key => $value)
$_POST['wmeet'][$key] = mysql_real_escape_string(stripslashes($value));
function dbSet($fields, $source = array()) {
$set='';
if (!source) $source = &$_POST;
foreach ($fields as $field) {
if (isset($source[$field])) {
$set.="`$field`='".mysql_real_escape_string($source[$field])."', ";
}
}
return substr($set, 0, -2);
}
$fields = explode(" ", "m_user m_pass m_email m_date m_ip m_type m_country m_place");
$query_mem = "INSERT INTO social_members SET ".dbSet($fields, $_POST['add']);
mysql_query($query_mem);
One way to try to take advantage of similar syntax between INSERT and UPDATE is to use the implode method mentioned by #KristerAndersson combined with REPLACE INTO. You'll have to have a primary key that is part of your insert or else a unique key for this to work. Since REPLACE INTO shares the same column syntax as INSERT INTO you can just use it and get your UPDATES along with your INSERTS. Keep in mind that REPLACE INTO deletes the old row and inserts a new one so the record key will change.
Try something like this:
if (is_array($_POST['add']))
foreach ($_POST['add'] as $key => $value)
$_POST['add'][$key] = mysql_real_escape_string(stripslashes($value));
if (is_array($_POST['wmeet']))
$_POST['add']['m_wmeet'] = mysql_real_escape_string(stripslashes(implode(' ',$_POST['wmeet'])));
function dbSet($fields, $source = array()) {
$set='';
if (!source) $source = &$_POST;
foreach ($fields as $field) {
if (isset($source[$field])) {
$set.="`$field`='".mysql_real_escape_string($source[$field])."', ";
}
}
return substr($set, 0, -2);
}
$fields = explode(" ", "m_user m_pass m_email m_date m_ip m_type m_country m_place m_wmeet");
$query_mem = "INSERT INTO social_members SET ".dbSet($fields, $_POST['add']);
mysql_query($query_mem);
this is what i've put together from the comments/answers ... which one would work?
if (is_array($_POST['wmeet']))
foreach ($_POST['wmeet'] as $key => $value)
$_POST['wmeet'][$key] = mysql_real_escape_string(stripslashes($value));
$wmeet = implode(" ",$_POST['wmeet']);
or does it need to be something like
$_POST['wmeet'][$key] =
mysql_real_escape_string(stripslashes($value = implode(" ",$_POST['wmeet'])));
and then the insert code... would something like this work?
$query_mem = "INSERT INTO social_members
SET ".dbSet($fields, $_POST['add']. ", m_wmeet=".$wmeet);