I have three fields, I need to update only the fields that are filled. The possible solution would be the following:
<?php
if(trim($_POST['field_1'])!='')
// query for update field 1
if(trim($_POST['field_2'])!='')
// query for update field 2
if(trim($_POST['field_3'])!='')
// query for update field 3
?>
But it is not the best optimization, Can you give me an example on how to do it with a single query using mysqli (with bind) or PDO?
You could build the query dynamically.
$fields = array();
foreach($_POST as $key => $value) {
// Only grab whitelisted fields
if (in_array($key, array('field_1', 'field_2', 'field_3'))) {
if (!empty(trim($value))) {
// Using the keys from $_POST assumes they are named after their database counterparts
$fields[$key] = trim($value);
}
}
}
// Grab the keys (fieldnames) so we can use them to build the query
$keys = array_keys($fields);
$sqlFieldsPart = implode(', ', array_map(function($field) {
return $field . '= :' . $field;
}, $keys));
$sql = sprintf('UPDATE tablename SET %s WHERE somefield=:somefield', $sqlFieldsPart);
$dbh = new PDO('mysql:hostname=localhost;dbname=yourdb', 'username', 'password');
$stmt = $dbh->prepare($sql);
// Modify keys on $fields
$data = array();
foreach ($fields as $key => $value) {
// Note the colon before the key variable, this is necessary
// It links to the placeholders in the query
$data[':' . $key] = $value;
}
// Set the value for the where clause
$data[':somefield'] = 'somevalue';
// Execute the statement, passing the data to the execute function
$stmt->execute($data);
This code assumes that your html fields are named after their database counterparts. If this is not the case, you can do the stuff from the first foreach loop hardcoded for each field or make some kind of field mapping.
I'm not sure if $_POST contains only the relevant fields or more, so I will assume you will find a way to isolate those in a $tmp array and use that instead.
I will also assume that you have already made a connection to the DB with PDO, and stored it in $db.
Finally, your row filter (where clause) is already built as a string in $rowfilter.
// trim all values
array_map('trim',$tmp);
// eliminate empty string values
$tmp=array_filter($tmp,function($el){return $el!='';});
// build the query string
$fields=array_map(function($el){$el="`$el`=?";},array_keys($tmp));
$fldstr=implode(',',$fields);
$sql="UPDATE `mytable` SET $fldstr WHERE $rowfilter";
// prepare and execute
$stmt = $db->prepare($sql);
$stmt->execute(array_values($tmp));
Related
I was told that "There is no way to bind an array to an SQL statement using prepared statements" but I have done it. I am having trouble recreating it though.
I have a statement that updates the database:
if (isset($_POST['printRow'])){
$ids = "";
foreach ($_POST['checkbox'] as $rowid)
{
if(!empty($ids)) $ids .= ',';
$ids .= $rowid;
$_SESSION['ids'] = $ids;
}
}
Here I forgot to post the WORKING code:
$stmt = $conn->prepare("UPDATE just_ink SET deleted=1 WHERE ID IN( " . $ids . ")");
$stmt->execute();
But I still have the following problem:
Where $ids can be either one or multiple ids.
So here is the problem, if I try to take $ids and set a SESSION with it
($_SESSION['ids'] = $ids;)
For use on another page.
On the next page I want to select data using $_SESSION['ids'] so,
$stmt = $conn->prepare("SELECT * FROM just_ink WHERE ID IN( " . $_SESSION['ids'] . ")");
$stmt->execute();
But this doesn't work. Any ideas why?
It doesn't work, because, as you correctly said, you can't bind an array to an SQL statement using prepared statements.
The correct way to bind an array is to create a string of placeholders (question marks) and then bind params in a loop.
Let's say you have an array of necessary ID's called $checkboxes. First, we need to create a string that we will use in our query to bind required params. If $checkboxes has 3 items, our string will look like
$placeholder = "?,?,?";
For this we can use str_repeat function to create a string, where every but last element will add ?, part to placeholder. For last element we need to concatenate single question mark.
$placeholder = str_repeat('?,', count($checkboxes)-1).'?';
Now we need to form and prepare a query that will contain our placeholders:
$query = 'SELECT * FROM just_ink WHERE ID IN (".$placeholder.")';
$stmt = $conn->prepare($query);
To bind every ID to its placeholder we use bindParam method in a loop:
for ($i=0; $i<count($checkboxes); $i++) {
$stmt->bindParam($i+1, ($checkboxes[$i]); #position is 1-indexed
}
$stmt->execute();
You can use arrays with mysqli prepared statements by using call_user_func_array
Your code would end up something like this
$varArray = array();
$questionArray = array();
foreach ($_POST['checkbox'] as $daNumber=>$daValue) {
$questionArray[] = "?";
//We're declaring these as strings, if they were ints, they would be i
$varArray[0] .= 's';
//These must be relational variables. The ampersand is vry important.
$varArray[] = &$_POST['checkbox'][$daNumber];
}
//comma separated series of questionmarks
$allDaQuestions = implode(', ', $questionArray);
$query = "SELECT * FROM just_ink WHERE ID IN ($allDaQuestions)";
$stmt = $conn->prepare($query);
//Where the magic happens
call_user_func_array(array(&$stmt, 'bind_param'), $varArray);
//continue with your regularly scheduled broadcast
$stmt->execute();
//etc.
did you set session_start() at the beginning of the file? you can't use $_SESSION if you don't do that first
database.php: //database class file
public function multipleInsert($table,$attrArray,$valuesArray) {
$sql = "INSERT INTO ".$table."(";
$array =[];
$appendValues = "";
$valuesInArray = "";
foreach ($attrArray as $key => $value) {
$sql.="".$value.", ";
}
$sql = substr_replace($sql,") VALUES ",strlen($sql)-2);
foreach ($valuesArray as $valArr) {
$valuesInArray.= "(";
foreach ($valArr as $key => $value) {
array_push($array, $value);
$valuesInArray.="?,";
}
$appendValues.= substr_replace($valuesInArray,"),",strlen($valuesInArray)-1);
$valuesInArray = "";
}
$appendValues = substr_replace($appendValues,"",strlen($appendValues)-1);
$sql.=$appendValues;
//die($sql);
$result = $this->executeQueryPRE($sql,$array);
return $result;
}
private function executeQueryPRE($sql,$arr) {
try{
$executeSQL = $this->Connection->prepare($sql);
print_r($executeSQL);die();
$executeSQL->execute($arr);
if($executeSQL) {
if($this->Connection->lastInsertId())
return $this->Connection->lastInsertId();
else
return true;
}
else
return false;
}
catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
}
sample.php // sample file which utilizing multiple insert query
require_once("database.php");
$Database = new Database;
$arr = ["ct_name","ct_num","ct_status"];
$arr1 = [["x","1234567890",1],["y","1234567890",1],["z","1234567890",1],["a","1234567890",1]];
$Database->multipleInsert("contact",$arr,$arr1);
Using PDO prepare statement, I am trying develop a dynamic multiple insert query. when I try to execute it, the values are getting inserted into table twice. I have gone for print_r($executeSQL) and die() option before executing it showed me a proper multiple insertion query as below.
PDOStatement Object ( [queryString] => INSERT INTO contact(ct_name,
ct_num, ct_status) VALUES (?,?,?),(?,?,?),(?,?,?),(?,?,?) )
why is it inserting twice and what is the reason and how can I overcome with this problem ?
Not an answer to your actual question but maybe to the actual problem you want to solve:
I don't think this string concat stuff is worth any trouble.
Takes longer for the php script to execute, pollutes the MySQL query cache, is error prone.
Therefore unless you can point to a very,very specific problem I think it loses on all points against: Just prepare a statement and execute it multiple times.
<?php
/*
table must be a valid table identifier
columns must be an array of valid field identifiers
recordData is an array of records, each itself an array of corresponding values for the fields in $columns
recordData is the only parameter for which proper encoding is taken care of by this function
*/
function foo($table, $columns, $recordData) {
$query = sprintf('
INSERT INTO %s (%s) VALUES (%s)
',
$table,
join(',', $columns) /* put in the field ids like a,b,c,d */,
join(',', array_pad(array(), count($columns), '?')) /* put in a corresponding number of ? placeholders like ?,?,?,? */
);
// resulting query string looks like INSERT INTO tablename (a,b,c,d) VALUES (?,?,?,?)
// let the MySQL server prepare that query
$stmt = $yourPDOInstance->prepare($query);
// it might fail -> check if your error handling is in place here....
// now just iterate through the data array and use each record as the data source for the prepapred statement
// this will (more or less) only transmit the statement identifier (which the MySQL server returned as the result of pdo::prepare)
// and the actual payload data
// .... as long as $yourPDOInstance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); has been set somewhere prior to the prepare....
foreach( $recordData as $record ) {
$stmt->execute( $record );
// might fail, so again: check your error handling ....
}
}
$cols = ["ct_name","ct_num","ct_status"];
$data = [
["x","1234567890",1],
["y","1234567890",1],
["z","1234567890",1],
["a","1234567890",1],
];
foo("contact", $cols, $data);
(script is tested by php -l only; no warranty)
see also: http://docs.php.net/pdo.prepared-statements
I have form with lot of inputs, and I'm trying to import them in database (mysql).
I want to use bind but trying to avoid writing all variables so many times. Probably I can't explain so good, so I will here is a code
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
if(!empty($firstName)&& !empty($lastName)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param('sss', $firstName, $lastName, $gender);
if($unos->execute()) {....
1.Well this is working fine , and it's not a problem, but now I want to add more inputs so I tried this
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('$firstName','$lastName','$gender');
$type='sss';
$param_list = implode(',', $param);
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param($type,implode(',', $param));
if($unos->execute()) {....
and it's not working. I get "Number of elements in type definition string doesn't match number of bind variables"...
I don't get it, because when I echo this implode thing I get what I need.
I'm pretty newbie with PHP, so help will be so precious. :)
You can try this:
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('firstName' => 's','lastName' => 's','gender' => 's');
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (". implode(',', array_keys($param) .") VALUES (". implode(',', array_fill(0, count($param), '?')) .")");
foreach($param as $paramName => $paramType) {
$unos->bind_param($paramType, $paramName);
}
if($unos->execute()) {....
You can pus as many parameters to $param array. Key should the name of the db column, value is its type.
You will need a variable for each question mark, but you will also need to bind each of the parameters separately. In your current situation, you bind a comma-separated list of values as a single string parameter.
What about this? I attempted to make the entire code rely on a single array of fields. If you want extra fields, you can just add them to the array and the rest of the code should respond to it. I don't have a proper test environment at hand and I typed this code by heart, so sorry for any typos. :)
// The fixes list of allowed/expected fields. Other values are ignored.
$fields = array('firstname', 'lastname', 'gender');
// Check if each value exists, and put them in an array.
$paramvalues = array();
foreach ($fields as $field) do
{
if (!isset($_POST[$field]))
die("missing field $field");
$paramvalues[] = & $_POST[$field]; // Bind_param wants a ref value, hence `&`
}
// Build a list of fields for the dynamic query.
$fieldlist = implode($fields, ',');
// And a list of placeholders.
$paramlist = implode(array_fill(0, count($fields), '?'), ',');
// And a list of types, assuming all parameters are strings.
$paramtypes = str_pad('', count($fields), 's');
// Prepare the query
$unos = $db->prepare("INSERT INTO members ($fieldlist) VALUES ($paramlist)");
// Build an array of reference values to be passed to call_user_func_array:
$paramrefvalues = array();
$paramrefvalues[] = $paramtypes
foreach ($paramvalues as $value) do
{
$paramrefvalues[] = & $value;
}
// Call bind_param using this array of by-ref parameters
call_user_func_array(array($unos, 'bind_param'), $paramrefvalues);
This code is loosely based on this article
I'm using a factory(class) to present forms from a target database table - as defined at class instance. Then on submit, create a new instance of the class which then insert a new record in to the database. $_POST key names match the table column names.
My issue is dynamically assigning bind parameters when the variables are determined at class instance. I'm getting the following, whether I use Reflections method or inline.
Warning: mysqli_stmt::bind_param() [mysqli-stmt.bind-param]: Number of elements in type definition string doesn't match number of bind variables
The following method is called in the sub class after the post array has been contructed and assigned to the class property $array.
private function addrecord($array,$tbl,$_conn){
//define field name array for query statement
foreach ($array as $key=>$value){
$keyarr[]=$key;
}
//BUILD THE QUERY STATEMENT
$query = "INSERT INTO $tbl SET ";
foreach ($keyarr as $key){
$query .= ($key."=?, "); //clone and add next element
}
$query = rtrim($query,", "); //remove EOL whitespace and comma
//done
/*
//Hard code bind parameters works as expected
if (self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME)){
$stmt=self::$_conn->prepare($query);
$stmt->bind_param("sssss",$array['user_id'],$array['user_name'],$array['user_email'],$array['user_date'],$array['user_active']);
$stmt->execute();
$insertid=$stmt->insert_id;
$stmt->close();
echo "The record was created with id ".$insertid;
}
*/
//Tried re assigning post array as reference
//same error as just passing $array
//$array = $this->refValues($array);
//Binding params using Reflections, same error
self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME);
$stmt = self::$_conn->prepare($query);
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt,$array);
$stmt->execute();
$stmt->close();
self::$_conn->close();
}
//Pass By Reference required for PHP 5.3+, dev server 5.3.17
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0){
$refar = array();
foreach($arr as $key => $value)
$refar[$key] = &$arr[$key];
return $refar;
}
return $arr;
}
Thanks in advance and much appreciated.
As you can see, mysqli is practically unusable with prepared statements.
So, I'd suggest you to either use PDO or, better, some intelligent library that can make safe query without prepared statments.
With such a library your function will be written in one line
private function addrecord($array,$tbl){
$this->conn->query("INSERT INTO ?n SET ?u", $tbl, $array);
}
please note that if $array is coming from the untrusted source, you have to filter it's content out first.
Per Common Sense, changed process to PDO. Works as expected. Should have done it sooner. Only issue remaining is UI feedback. Would like to return an insert id, however MySQL doesnt return a last insert id for PDO. And again, the class doesnt know the tables structure in advance so hard coding in not an option. Need a workaround. Any thoughts? Heres the new insert process using PDO.
try{
self::$_conn = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.'', DB_UNAME, DB_UPWORD);
$stmt = self::$_conn->prepare($query);
$arcount=count($array); //set number of bindParam loops
foreach($array as $key=>$value){
$stmtarr[]=$value; //convert assoc to numerated array
}
//re index array so increment will match up with placeholder position
$stmtarr = array_combine(range(1, count($stmtarr)), array_values($stmtarr));
for($i=1;$i<=$arcount;$i++){ //bind variable, one for each placeholder,
$stmt->bindParam($i,$stmtarr[$i]);
}
$stmt->execute();
} catch (PDOException $e){
print "Error: ".$e->getMessage()."<br>";
die();
}
Assume everything else is the same as above.
I have an array like this
$a = array( 'phone' => 111111111, 'image' => "sadasdasd43eadasdad" );
When I do a var-dump I get this ->
{ ["phone"]=> int(111111111) ["image"]=> string(19) "sadasdasd43eadasdad" }
Now I am trying to add this to the DB using the IN statement -
$q = $DBH->prepare("INSERT INTO user :column_string VALUES :value_string");
$q->bindParam(':column_string',implode(',',array_keys($a)));
$q->bindParam(':value_string',implode(',',array_values($a)));
$q->execute();
The problem I am having is that implode return a string. But the 'phone' column is an integer in the database and also the array is storing it as an integer. Hence I am getting the SQL error as my final query look like this --
INSERT INTO user 'phone,image' values '111111111,sadasdasd43eadasdad';
Which is a wrong query. Is there any way around it.
My column names are dynamic based what the user wants to insert. So I cannot use the placeholders like :phone and :image as I may not always get a values for those two columns. Please let me know if there is a way around this. otherwise I will have to define multiple functions each type of update.
Thanks.
Last time I checked, it was not possible to prepare a statement where the affected columns were unknown at preparation time - but that thing seems to work - maybe your database system is more forgiving than those I am using (mainly postgres)
What is clearly wrong is the implode() statement, as each variable should be handled by it self, you also need parenthesis around the field list in the insert statement.
To insert user defined fields, I think you have to do something like this (at least that how I do it);
$fields=array_keys($a); // here you have to trust your field names!
$values=array_values($a);
$fieldlist=implode(',',$fields);
$qs=str_repeat("?,",count($fields)-1);
$sql="insert into user($fieldlist) values(${qs}?)";
$q=$DBH->prepare($sql);
$q->execute($values);
If you cannot trust the field names in $a, you have to do something like
foreach($a as $f=>$v){
if(validfield($f)){
$fields[]=$f;
$values[]=$v;
}
}
Where validfields is a function that you write that tests each fieldname and checks if it is valid (quick and dirty by making an associative array $valfields=array('name'=>1,'email'=>1, 'phone'=>1 ... and then checking for the value of $valfields[$f], or (as I would prefer) by fetching the field names from the server)
SQL query parameters can be used only where you would otherwise put a literal value.
So if you could see yourself putting a quoted string literal, date literal, or numeric literal in that position in the query, you can use a parameter.
You can't use a parameter for a column name, a table name, a lists of values, an SQL keyword, or any other expressions or syntax.
For those cases, you still have to interpolate content into the SQL string, so you have some risk of SQL injection. The way to protect against that is with whitelisting the column names, and rejecting any input that doesn't match the whitelist.
Because all other answers allow SQL injection. For user input you need to filter for allowed field names:
// change this
$fields = array('email', 'name', 'whatever');
$fieldlist = implode(',', $fields);
$values = array_values(array_intersect_key($_POST, array_flip($fields)));
$qs = str_repeat("?,",count($fields)-1) . '?';
$q = $db->prepare("INSERT INTO events ($fieldlist) values($qs)");
$q->execute($values);
I appreciated MortenSickel's answer, but I wanted to use named parameters to be on the safe side:
$keys = array_keys($a);
$sql = "INSERT INTO user (".implode(", ",$keys).") \n";
$sql .= "VALUES ( :".implode(", :",$keys).")";
$q = $this->dbConnection->prepare($sql);
return $q->execute($a);
You actually can have the :phone and :image fields bound with null values in advance. The structure of the table is fixed anyway and you probably should got that way.
But the answer to your question might look like this:
$keys = ':' . implode(', :', array_keys($array));
$values = str_repeat('?, ', count($array)-1) . '?';
$i = 1;
$q = $DBH->prepare("INSERT INTO user ($keys) VALUES ($values)");
foreach($array as $value)
$q->bindParam($i++, $value, PDO::PARAM_STR, mb_strlen($value));
I know this question has be answered a long time ago, but I found it today and have a little contribution in addition to the answer of #MortenSickel.
The class below will allow you to insert or update an associative array to your database table. For more information about MySQL PDO please visit: http://php.net/manual/en/book.pdo.php
<?php
class dbConnection
{
protected $dbConnection;
function __construct($dbSettings) {
$this->openDatabase($dbSettings);
}
function openDatabase($dbSettings) {
$dsn = 'mysql:host='.$dbSettings['host'].';dbname='.$dbSettings['name'];
$this->dbConnection = new PDO($dsn, $dbSettings['username'], $dbSettings['password']);
$this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
function insertArray($table, $array) {
$fields=array_keys($array);
$values=array_values($array);
$fieldlist=implode(',', $fields);
$qs=str_repeat("?,",count($fields)-1);
$sql="INSERT INTO `".$table."` (".$fieldlist.") VALUES (${qs}?)";
$q = $this->dbConnection->prepare($sql);
return $q->execute($values);
}
function updateArray($table, $id, $array) {
$fields=array_keys($array);
$values=array_values($array);
$fieldlist=implode(',', $fields);
$qs=str_repeat("?,",count($fields)-1);
$firstfield = true;
$sql = "UPDATE `".$table."` SET";
for ($i = 0; $i < count($fields); $i++) {
if(!$firstfield) {
$sql .= ", ";
}
$sql .= " ".$fields[$i]."=?";
$firstfield = false;
}
$sql .= " WHERE `id` =?";
$sth = $this->dbConnection->prepare($sql);
$values[] = $id;
return $sth->execute($values);
}
}
?>
dbConnection class usage:
<?php
$dbSettings['host'] = 'localhost';
$dbSettings['name'] = 'databasename';
$dbSettings['username'] = 'username';
$dbSettings['password'] = 'password';
$dbh = new dbConnection( $dbSettings );
$a = array( 'phone' => 111111111, 'image' => "sadasdasd43eadasdad" );
$dbh->insertArray('user', $a);
// This will asume your table has a 'id' column, id: 1 will be updated in the example below:
$dbh->updateArray('user', 1, $a);
?>
public function insert($data = [] , $table = ''){
$keys = array_keys($data);
$fields = implode(',',$keys);
$pre_fields = ':'.implode(', :',$keys);
$query = parent::prepare("INSERT INTO $table($fields) VALUES($pre_fields) ");
return $query->execute($data);
}