Using bind for form with lot of inputs (PHP) - php

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

Related

How to prepare SQL query dynamically (column names too) avoiding SQL injection

I recently learned about SQL Injection and the PHP recommendation to avoid it, using prepare() and bind_param().
Now, I want to prepare SQL queries dynamically, adding both column names and values.
I usted to do it like this, having the name field of the HTML input with the same name as the MySQL database column.
<input type="text" name="firstname" >
<input type="text" name="lastname" >
And the, create the SQL query dynamically using mysqli.
// Extract values from POST
$parameters = $_POST;
// Organize the values in two strings
foreach ($parameters as $id => $value) {
$fields = $fields . "`" . $id . "`,";
$values = $values . "'" . $value . "',";
/*e.g.
$fields = `firstname`,`lastname`
$values = 'John','Wick'
*/
}
// Write into the database
$sql = "INSERT INTO `user` ($fields) VALUES ($values)";
/*e.g.
INSERT INTO `user` (`firstname`,`lastname`) VALUES ('John','Wick')
*/
I would like to know if there is a way to do this using prepare() and bind_param() to avoid SQL injection, may be adding adding some data-type="s" to the HTML input tag or if there is a better, more best-practices, way to do it.
You can use bound parameters only for an element that would be a constant value — a quoted string, a quoted datetime, or a numeric literal.
You can't use a parameter placeholder for anything else in SQL, like column names, table names, lists of values, SQL keywords or expressions, or other syntax.
If you need to make column names dynamic, the only option is to validate them against a list of known columns.
$columns_in_user_table = [
'userid'=>null,
'username'=>'',
'firstname'=>'',
'lastname'=>''
];
// Extract values from POST, but only those that match known columns
$parameters = array_intersect_key($_POST, $columns_in_user_table);
// Make sure no columns are missing; assign default values as needed
$parameters = array_merge($columns_in_user_table, $parameters);
If you use PDO instead of mysqli, you can skip the binding. Just use named parameters, and pass your associative array of column-value pairs directly to execute():
$columns = [];
$placeholders = [];
foreach ($parameters as $col => $value) {
$columns[] = "`$col`";
$placeholders[] = ":$col";
}
$column_list = implode($columns, ',');
$placeholder_list = implode($placeholders, ',');
// Write into the database
$sql = "INSERT INTO `user` ($column_list) VALUES ($placeholder_list)";
$stmt = $pdo->prepare($sql);
$stmt->execute($parameters);
I noticed you included the mysqli tag on your question, so assuming your database is MySQL and you are using the native MySQL functions, then you can do something like this:
$stmt = mysqli_prepare($link, "INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
mysqli_stmt_bind_param($stmt, 'sssd', $code, $language, $official, $percent);
$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;
/* execute prepared statement */
mysqli_stmt_execute($stmt);
And yes, I ripped that straight out of the PHP manual page on mysqli_stmt_bind_param.

PHP Prepared Statement - Dynamic Vars Number of Element Error

My previous question was closed because they said it was a duplicate but the duplicate posts did not answer my question. So here I go again and I put some additional comments in the edit section to state why the duplicate posts did not help me.
I am trying to construct a prepared statement dynamically and I keep getting the following error:
mysqli_stmt_bind_param(): Number of elements in type definition string
doesn't match number of bind variables in
When I echo my statements the number of type definitions matches the bind variable so I don't know what is wrong. I think my code may be passing in strings, quotes or something instead of variables but I'm new to prepared statement and not sure how to check my query. When using simple mysqli_query I can echo the query and see were my error is at. I'm not sure how to do this with prepared statements so I'm hoping someone can help uncover my error.
I am trying to construct the prepares statement dynamically so that I can reuse the code as follows:
$db = mysqli_stmt_init($dbconnection);
// I have looped through my fields and constructed a string that when
// echoed returns this:
// ?, ?, ?, ?,
// I use sub str just to remove the last comma and space leaving me
// with the string
// ?, ?, ?, ?.
// Ive echoed this to the browser to make sure it is correct.
$preparedQs = substr($preparedQs, 0, -2);
// I then loop through each field using their datatype and constructs
// the type string as follows ssss. Ive echoed this to the browser to
// make sure it is correct.
$preparedType = 'ssss';
// I then loop through my post array verifying and cleaning the data
// and then it constructing a string of clean values that results in
// Mike, null, Smith, Sr., (First, Middle, Last, Suffix) I use substr
// again just to remove the last comma and space. Ive echoed this to
// the browser to make sure it is correct.
$cleanstr = substr($cleanstr, 0, -2);
// I then explode that string into a an array that I can loop through
// and assign/bind each value to a variable as follows and use substr
// again to remove last comma and space.
$cleanstr = explode(", ", $cleanstr);
$ct2 = 0;
foreach ( $cleanstr as $cl){
$name = "a".$ct2;
$$name = $cl;
$varstr .= "$".$name.", ";
$ct2 = $ct2 +1;
}
$varstr = substr($varstr, 0, -2);
// I've echoed the $varstr to the browser and get $a1, $a2, $a3, $a4.
// I have also echo their value outside of the loop and know values
// have been assigned.
// I then try to assign each step above the appropriate
// prepared statement place holder
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, "'".$preparedType."'", $varstr);
mysqli_stmt_execute($stmt);
I'm am not sure what I am doing wrong because when I echo $preparedQs, $preparedType and $varstr they all have the same number of elements yet I'm getting the "mysqli_stmt_bind_param(): Number of elements in type definition string doesn't match number of bind variables in.." error. All i can think is that I have quotes or something where I shouldn't but I've tried adding and removing quotes in certain areas and cant get the error to resolve.
Also, I read some posts about passing null in prepared statement but even when I replace the null with an actual value, I still get the same error.
It's probably worth noting that when using simple procedural mysqli_query and mysqli_real_escape_string to clean my data things work fine. I am trying to improve my security by converting my application to prepared statement simply for the added security.
This question is different for two reasons
I am using procedural coding and not object or PDO. So being new to prepared statements, the examples given aren't helpful even after trying to make sense of them.
I am using an insert statement, not a select or update statement which in procedural php the query string is written differently for insert than for select or update statements.
//UPDATED CODE
global $dbconnection;
if(!$dbconnection){
die("Function wm_dynamicForm connection failed.</br>");
} else {
//echo "</br>Function wm_connectionToDatabase connection success</br>";
}
$db = mysqli_stmt_init($dbconnection);
$preparedQs = substr($preparedQs, 0, -2); //removes the end , from my string
$cleanstr = substr($cleanstr, 0, -2); //removes the end , from my string
$cleanstr = explode(", ", $cleanstr);
$ct = 0;
foreach ( $cleanstr as $cl){
$items[] = array(
'a'.$ct => $cl,
);
$ct = $ct + 1;
}
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, $preparedType, ...$items);
mysqli_stmt_execute($stmt);
if(!mysqli_stmt_execute($stmt)){
echo "Error: ".mysqli_error($db);
}
You could do dynamic bind with php 5.6 feature called unpacking operator/elipsies the ....
$db = mysqli_connect('localhost', 'root', 'pass', 'database');
$data = array('name' => 'foo', 'age' => 99, 'email' => 'abc#abc.com');
$stmt = mysqli_stmt_prepare($db, "INSERT INTO Contacts VALUES (". $preparedQs. ")");
mysqli_stmt_bind_param($db, $preparedType, ...$data);
mysqli_stmt_execute($stmt);
I've been here before, dynamic prepared statement, dynamic query preparation.
The problem is not your code so far, the problem is the array of your sql fields you dynamically prepared to bind.
The index of that array starts with zero(0), but the index of your bindValue needs to start with one(1).
So what you will do is to make your field array index to start with 1.
In php you can force defaul index of an array to start with 1 instead of zero.
If am not wrong you have:
dbfield="username, password, name"
dbvalue="?, ?, ?"
and you have an array of input value you are looping with like:
foreach($inputarray as $key=>$value){
// key index must start from 1
//now you can bind
bindValue($key, $value);
}
If am flowing hit me answer accept.
try to use like this in prepared statement.
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "test";
$conn = new mysqli($servername, $username, $password, $dbname);
$cleanstr = "John,Dew,Doe,Sr.";
$cleanstr = explode(",", $cleanstr);
$varstr=array();
foreach($cleanstr as $cl){
$varstr[] = "$".$cl;
}
$operation = "INSERT INTO Contacts (firstname, middlename, lastname, suffix) VALUES (?, ?, ?, ?)";
$callfunc = insertCommon($conn,$varstr, $operation);
function insertCommon($conn,$varstr, $operation){
$types = "";
foreach($varstr as $value)
$types .= "s";
$varstr = array_merge(array($types),$varstr);
$insertQry = $conn->prepare($operation);
$refArray = array();
foreach($varstr as $key => $value) $refArray[$key] = &$varstr[$key];
call_user_func_array(array($insertQry, 'bind_param'), $refArray);
$insertQry->execute();
return true;
}

How to prepend colon (:) and append (,) array values in php?

I am trying to create simple PDO mysql insert query statement in most clean and efficient way.
My question is that, is there any simple function in php that could both append and prepend array values and convert it to a string like implode() function do? Or am i doing it right-(See last working code i provided)
Correct me if i am wrong, implode just adds a single string inbetween each value before converting to a string.Hope my code will clear out more what i am trying to achieve.
//key as field name value as value
$field_values=array(
"firstname"=>$fname,
"lastname"=>$lname,
"phone"=>$phone);
//field name would work fine as just a comma is appended to each array key
//i need,if there is an inline way to perform array values to start with colon and end with comma.
//this doesn't work as not comma is added inbetween
$sql="INSERT INTO student (".implode(",",array_keys($field_values).") VALUES (".implode(":",$field_values).")";
//result wrong no commas
//INSERT INTO student (firstname,lastname,phone) VALUES (:firstname:lastname:phone);
//i want this
//INSERT INTO student (firstname,lastname,phone) VALUES (:firstname,:lastname,:phone);
i can't use this too
$sql="INSERT INTO student (".implode(",",array_keys($field_values).") VALUES (?,?,?);
i am using this to bind
foreach($field_values as $field=>$value)
{
$dbh->bindValue(':'.$field,$value);
}
I know i could achieve this with just writing a string like :firstname,:lastname,:phone or may be with regex or use a foreach loop to append and prepend like this.This would work fine but its lengthy
$fv="";
foreach($field_values as $field=>$value)
{
$fv.=":".$field.",";
}
$fv=rtrim($fv, ",");
$sql="INSERT INTO student (".implode(",",$field_values).") VALUES (".$fv.")";
But what i want to know whether there is a way to do that something like the code i used above. in the scenario the array keys may change, so i am searching for a dynamic efficient way.
Help me please :)
Any help is appreciated.
Thank you in advance.
This PDO Insert function will accept an array as input.
// Insert an array with key-value pairs into a specified MySQL database table.
function pdo_insert($dbh,$table,$keyvals) {
$sql = sprintf("INSERT INTO %s ( `%s` ) %sVALUES ( :%s );",
$table,
implode("`, `", array_keys($keyvals)),
implode(", :", array_keys($keyvals))
);
$stmt = $dbh->prepare($sql);
foreach ($keyvals as $field => $value) {
$stmt->bindValue(":$field", $value, PDO::PARAM_STR);
}
$stmt->execute();
return $dbh->lastInsertId();
}
// Convert special characters to HTML safe entities.
function h($str) {
return trim(stripslashes(htmlspecialchars($str, ENT_QUOTES, 'utf-8')));
}
Example:
$dbh = new PDO($dsn);
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$keyvals = [
'id' => isset($_POST['id']) ? h( $_POST['id'] ) : null,
'title' => isset($_POST['title']) ? h( $_POST['title'] ) : null,
'description' => isset($_POST['description']) ? h( $_POST['description'] ) : null,
'created_at' => time(),
'created_by' => 1,
];
$last_ids[] = pdo_insert($dbh,'products',$keyvals);
if you dont want to use a foreach to add in the : for the column names you can try something like this
$fname = "first";
$lname = "last";
$phone = "2342344";
$field_values=array(
"firstname"=>$fname,
"lastname"=>$lname,
"phone"=>$phone
);
echo "INSERT INTO student (`:".implode("`,:`",$field_values)."`) VALUES (".$fv.")";
as you can see in the insert columns i added a `: to the being and end in the columns bracket

PHP PDO - single query for multiple x fields

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));

PDO: bindParam empty string

Theres's a similar question here, but actually that doesn't give me the answer:
PHP + PDO: Bind null if param is empty
I need my statement work in a loop, with only changing the binded variables.
Like:
$this->array = array(
"cell1" => "",
"cell2" => "",
);
$this->sth = $db->prepare("INSERT INTO `table`
(`coloumn1`, `coloumn2`)
VALUES (:coloumn1, :coloumn2)");
$this->sth->bindParam(:coloumn1, $this->array['cell1'], PDO::PARAM_STR);
$this->sth->bindParam(:coloumn2, $this->array['cell2'], PDO::PARAM_STR);
//Data proccessing...
foreach($data as $value){
$this->array['cell1'] = $value['cell1'];
$this->array['cell2'] = $value['cell2'];
try {
this->sth->execute();
print_r($this->sth->errorInfo());
}
catch(PDOException $e){
echo 'sh*t!';
}
}
Everything works well until either of the values is an empty string.
My problem is when 'cell1' is an empty string, the bound parameter is a nullreference, and it won't work. But I need the referenced binding because of the loop, so bindValue isn't a solution.
And I need the loop very bad, because of the huge data I want to process.
Any suggestion?
I tried right before execute:
foreach($this->array as $value){
if(!$value) {
$value = "";
}
}
It doesn't work.
The only way that solved my problem is modifying to this:
$this->array['cell1'] = !empty($value['cell1']) ? $value['cell1'] : "";
$this->array['cell2'] = !empty($value['cell2']) ? $value['cell2'] : "";
But this seems too rubbishy...
I know its necroposting, but maybe will help to someone.
You trying to check if the variable false, but not null. And not reapplying values back to array.
foreach($this->array as $value){
if(!$value) {
$value = "";
}
}
Try to check for null in loop
foreach($this->array as $index => $value)
{
$this->array[$index] = !empty($value) ? $value : '';
}
Your question has nothing to do with PDO but with basic PHP.
When there is no variable available - you can't use it at all. So, you have to create it somehow. The way you are using at the moment is not "rubbishy" but quite acceptable. I'd rather call whole code "rubbishy" as it's twice as big as as it should be.
But I need the referenced binding because of the loop, so bindValue isn't a solution.
This assumption is wrong too. Why do you think you can't use bind by value?
$sql = "INSERT INTO `table` (`coloumn1`, `coloumn2`) VALUES (?, ?)";
$sth = $db->prepare($sql);
foreach($data as $value)
{
$value['cell1'] = !empty($value['cell1']) ? $value['cell1'] : "";
$value['cell2'] = !empty($value['cell2']) ? $value['cell2'] : "";
$sth->execute($value);
}
as simple as this
And I need the loop very bad, because of the huge data I want to process.
I don't think it's really huge, as it fits for the PHP process memory. However, consider to use LOAD DATA INFILE query for the real huge amounts.

Categories