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
Related
public function add_employee($input)
{
$key_array = null;
$value_array = null;
$bind_array = null;
foreach ($input as $column => $value) {
if ($value) {
#$bind_array => ?, ?, ?;
#$value_array => [$value1, $value2, $value3];
#$key_array => column1, column2, column3;
}
}
$sql = "INSERT INTO ol_employee ($key_array) VALUES ($bind_array)";
$this->db->query($sql, $value_array);
}
Refer to comment in the function, how to achieve that output?
the idea is, from the input POST i get which over 27 fields, i just want to fill in into the $sql query i prepared as you can see. I don't think writing each table column manually is a good way.
im using Codeigniter 4 php framework + postgresql.
According to CodeIgniter 4 documentation, you can do this inside your loop for each employee:
$data = [
'title' => $title,
'name' => $name,
'date' => $date
];
$db->table('mytable')->insert($data);
You can use array and implode function first make array of key_array, value_array and bind_array then use implode() and use in sql like following
public function add_employee($input)
{
$key_array = array();
$value_array = array();
$bind_array = array();
foreach ($input as $column => $value) {
if ($value) {
$bind_array[] = '?';
$value_array[] = $value;
$key_array[] = $column;
}
}
$binds = implode(",",$bind_array);
$keys = implode(",",$key_array);
$values = implode(",",$value_array);
$sql = "INSERT INTO ol_employee ($keys) VALUES ($binds)";
$this->db->query($sql,$values);
}
by the insight of Alex Granados, this is the query i use:
public function add_employee($input)
{
foreach ($input as $column => $value) {
if ($value) {
$data[$column] = $value;
}
}
$this->db->table('ol_employee')->insert($data);
}
this will eliminate null field and regardless how many field, still working good.
as long the input name field from form is same as db column. Else, need to do some changes on that.
Thanks guys.
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);
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.
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);