Prepare MySQL statement with IN() function - php

Now I do:
$params = array(1,2,3);
$sql = 'select * from foo where bar in (%s)';
$sql = sprintf($sql,
implode(',', $params)
);
$params is supplied by a user so it's obviously unsafe.
How can I fix this? I would prefer using a framework like Zend.

You could use prepared statements with PDO:
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
$params = array(1,2,3);
$values = implode(',', array_fill(0, count($params), '?')); // ?,?,?
$sql = "select * from foo where bar in ($values)";
$stmt = $dbh->prepare( $sql );
$stmt->execute( $params );
By using prepared statements, you avoid the need to escape your data. You will still need to validate it though.

You have to make each array entry safe. Either using mysql_real_escape_string(), or in your case, just by casting to int. The most generic method would be the following:
function quoteString( $string ) {
return "'" . mysql_real_escape_string($string) . "'";
}
$quotedParams = array_map("quoteString", $params);
$sql = sprintf($sql,
implode(',', $quotedParams)
);
In your case, you could cast to int, so you could do it like this:
$intParams = array_map("intval", $params);
$sql = sprintf($sql,
implode(',', $intParams)
);

Related

Search More than 1 value inside 1 input text of HTML search form

I have an usual search form using HTML that will be extract the data from mysql (the connection is using PHP and mysqli). The function works well in searching 1 value, but I want to make user can search more than 1 value that will be separated by comma (,).
For example:
Searching 1 value works well.
But I want to search 2 or more values inside the search box, like: 42-7278954,53-1217544,07-2517487,...
I hope user can input something like in the pic below and the result will have 2 rows -->
CN_no 42-7278954 and 53-1217544:
The query I have so far is:
$sql = "SELECT * FROM mock_data WHERE CN_no IN ('{$CN_no}') OR doc_no IN ('{$doc_no}')";
Notes: CN_no is "Shipment Code" and doc_no is "Reference No"
But well... it's obviously give me an error because of the incorrect syntax.
Please help me to revise it. Thank you.
======== update query based on vp_arth's answer ========
$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';
$query = "SELECT * FROM mock_data WHERE CN_no IN ({$incn}) or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));
But it give me an error
This is a possible solution to use prepared statements with dynamic input in mysqli. The typing for the parameter binding is static though, in this case the parameters are strings.
/**
* connecting to the database
* defining in how manye columns you want to search (important to create the correct amount of arguments)
*/
$conn = new mysqli('localhost', 'root', '', 'test');
$columnsToSearch = 2;
/**
* the numbers you want to search delimited by ","
*/
$CN_no = '42-7278954,53-1217544,07-2517487';
$cn = explode(',', $CN_no);
/**
* writing the numbers to search into variables
* putting the references of those variables into an array
*
* the references will be used as arguments for the prepared statement
*/
$values = array();
for ($i = 0; $i < $columnsToSearch; $i++) {
foreach ($cn as $k => $value) {
$temp{$k}{$i} = $value;
$values[] = &$temp{$k}{$i};
}
}
/**
* putting together the "types"-part for the binding of the prepared statement
*/
$types = array(str_repeat('s', count($cn) * $columnsToSearch - 1) . 's');
/**
* merging types and references
*/
$argumentsArray = array_merge($types, $values);
/**
* creating placeholder string for the query
*/
$placeholder = str_repeat('?, ', count($cn) - 1) . '?';
$stmt = $conn->prepare('SELECT CN_no, doc_no FROM mock_data WHERE CN_no IN (' . $placeholder . ') or doc_no IN (' . $placeholder . ')');
/**
* check http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 to read what is happening here
*/
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt, $argumentsArray);
$stmt->execute();
/**
* fetching the result
*/
$stmt->bind_result($CN_no, $doc_no);
$row_set = array();
while ($row = $stmt->fetch()) {
$row_set[] = array('CN_no' => $CN_no, 'doc_no' => $doc_no);
}
var_dump($row_set);
exit;
I adjusted the comment from http://us3.php.net/manual/en/mysqli-stmt.bind-param.php#104073 so it fits to your scenario.
Btw. With PDO as database-API this would be A LOT easier to write and to read. I might add an example for PDO later.
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
// search input
$input = '42-7278954,53-1217544,07-2517487';
// create and filter your search array
$iArr = array_filter(array_map('trim', explode(",", $input)), 'strlen');
// create your search string from CN_no array
$CN_no = implode("|",$iArr);
/* create a prepared statement */
if ($stmt = $mysqli->prepare("SELECT * FROM `mock_data` WHERE `CN_no` RLIKE ?")) {
/* bind parameters for search */
$stmt->bind_param("s", $CN_no);
/* execute query */
$stmt->execute();
// get all the rows returned
// IMPORTANT
// Use the below syntax in case you do use native mysqlnd driver.
// If you don't have mysqlnd installed/loaded, you will get an error
// that "mysqli_stmt_get_result()" method is undefined.
$result = $stmt->get_result()->fetch_all();
// in case you don't have native mysqlnd loaded uncomment
// and try the below syntax to get the result
/* $meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$result[] = $c;
} */
/* *********** */
// print output
print_r($result);
/* close statement */
$stmt->close();
}
/* close connection */
$mysqli->close();
Use FIND_IN_SET() function
Syntax : FIND_IN_SET (YOUR_INPUT_STRING_NAME, COLUMN_NAME);
YOUR_INPUT_STRING_NAME it may be 42-7278954,53-1217544,07-2517487,...
Let me know if it helps you.
Some abstract php code:
$cn = explode(',', $input1);
$incn = str_repeat('?, ', count($cn)-1).'?'
$doc = explode(',', $input2);
$indoc = str_repeat('?, ', count($doc)-1).'?'
$sql = "SELECT ... WHERE cn_no IN ({$incn}) or doc_no IN ({$indoc})";
$result = $db->rows($sql, array_merge($cn, $doc));
$input = '42-7278954,53-1217544,07-2517487';
$inputs = explode(',', $input);
$new_inputs = array_map(function ($item){
$item = trim($item);
return "'" . $item . "'";
}, $inputs);
$string = implode(',', $new_inputs);
// string is '42-7278954','53-1217544','07-2517487'
$sql = "SELECT * FROM mock_data WHERE CN_no IN ({$string})";
$input = '42-7278954,53-1217544,07-2517487';
echo $strNew=str_replace(",","','",$input);
$sql = "SELECT * FROM mock_data WHERE CN_no IN ('".$strNew."')";
$row=mysqli_query($conn,$query)or die("not fire");
try this code
It seems that in-clause in not getting generated correctly by your implementation.
To generate the in-clause correctly, you need to split the user input into array and then join back in-list. For example, if user have given 42-7278954,53-1217544,07-2517487 as input, in-list should be look like:
CN_No in ( '42-7278954' , '53-1217544' , '07-2517487');
Given that Shipment code and Reference code are string value, you have to enclose them in quotes ' '.
Following example may help you to generate in-list expression using implode and explode (php is very little known to me)
$in_list_cns = implode( "' , '", explode(",", $CN_no));
$sql = "SELECT * FROM mock_data WHERE CN_no IN ( '{$in_list_cns}')"
Hope this help.
Also, as mentioned by other too, you may need to sanitize user input to prevent SQL-Injection attacks.
try
$cn = explode(',', $CN_no);
$incn = str_repeat('?, ', count($cn)-1).'?';
$doc = explode(',', $doc_no);
$indoc = str_repeat('?, ', count($doc)-1).'?';
$query = "SELECT * FROM mock_data WHERE CN_no IN ('.$incn.') or doc_no IN ({$indoc})";
$result = $conn->query($query , array_merge($incn, $indoc));

Binding array of values in prepared statement PDO PHP

I'm trying to bind my values into a prepared statement in PDO.
Here is the pre requisite codes that that uses the prepared statement block:
$tab = 'air_user';
$fie = array('USER_NAME', 'USER_PASSWORD' , 'USER_EMAIL');
$name = $_POST['name'];
$pass = $_POST['password'];
$email = $_POST['email'];
$val = array(
'name' => $name,
'pass' => $pass,
'email' => $email
);
$this->connect($tab,$fie,$val);
And here is the part wherein I prepare those values and make the necessaru insertions:
public function connect($table,$fields,$values)
{
try{
$con = new PDO ('mysql:host=localhost;dbname=air','root','123456');
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$con->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$fields = implode(", ", $fields);
echo $fields;
$values = implode(", ", $values);
echo $values;
// have to make this prevent sql injection //
$stmt = $con->prepare("INSERT INTO $table(ID,$fields) VALUES (?,?,?,?)");
$stmt->execute(array('',$values));
} catch(PDOException $e) {
die("this cant connect the database");
}
}
so why isit my INSERT not Working ? isit can anyone help me take a look of it , i tryed so many things , none of them work.
No, don't implode the values that your going to pass inside the ->execute(), it must be an array:
$fields = implode(", ", $fields);
// $values = implode(", ", $values); // DONT IMPLODE!
$values = array_values($values);
$stmt = $con->prepare("INSERT INTO $table(ID,$fields) VALUES (NULL, ?,?,?)");
$stmt->execute($values);
Or #Augwa's suggestion:
// $fields = implode(", ", $fields); // not needed
// $values = implode(", ", $values); // DONT IMPLODE!
$placeholders = substr(str_repeat('?,', sizeOf($fields)), 0, -1);
// $placeholders = implode(', ', array_fill(0, count($values), '?'));
$stmt = $con->prepare(
sprintf(
"INSERT INTO %s (%s) VALUES (%s)",
$table,
implode(',', $fields),
$placeholders
)
);
$stmt->execute($values);

No data returned with PDO

Here's the relevant piece of my PHP code:
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//get values from AJAX
$whereCategory = isset($_GET['cat_code'])? "{$_GET['cat_code']}" : '';
$sortvalue = isset($_GET['sortvalue'])? "{$_GET['sortvalue']}" : '';
$sortorder = isset($_GET['sortorder'])? "{$_GET['sortorder']}" : '';
$sql = "select * from {$table} where cat_code = ':cat_code' order by ':sortvalue' ':sortorder';";
$stmt2 = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY) );
$stmt2->execute(array(':cat_code' => $whereCategory,':sortvalue' => $sortvalue, ':sortorder' => $sortorder));
$result = $dbh->query($sql)->fetchAll(PDO::FETCH_ASSOC);
header('Content-type: application/json');
echo json_encode($result);
If I var_dump the variables $where_category,$sortorder, and $sortvalue, they are all what I expect and the correct data that would need to be passed in the query. I've tried the query directly without PDO just substituting in the correct variables and I get back what I want, but apparently I'm not sending the variables correctly with my PDO methods (such as they are).
I'm getting no errors back, but no data returned either.
Any suggestions?
First off, named placeholders doesn't need to be quoted, so ditch those.
Secondly, you cannot bind identifiers (table/columns/DESC/ASC) You could only whitelist those.
Third, don't mix ->query() and ->execute(). Use ->execute() alone:
header('Content-type: application/json');
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//get values from AJAX
if(isset($_GET['cat_code'], $_GET['sortvalue'], $_GET['sortorder'])) {
$whereCategory = $_GET['cat_code'];
$sortvalue = $_GET['sortvalue'];
$sortorder = $_GET['sortorder'];
// super simple filtering
$default_tables = array('table1', 'table2', 'table3');
$default_columns = array('column1', 'column2', 'column3');
in_array(needle, haystack)
if(
in_array($table, $default_tables) &&
in_array($sortvalue, $default_columns) &&
in_array($sortorder, array('ASC', 'DESC'))
) {
// good to go
$sql = "SELECT * FROM $table where cat_code = :cat_code ORDER BY $sortvalue $sortorder";
$stmt2 = $dbh->prepare($sql);
$stmt2->execute(array(':cat_code' => $whereCategory));
echo json_encode($stmt2->fetchAll(PDO::FETCH_ASSOC));
} else {
// did not satisfy condition
}
}
Sidenote: Those default tables and columns are just examples, you'll need to populate and correspond it into yours. You could create your own method/function which creates a map with tables with their corresponding columns if you really want to be sure of it.
Change this -
$result = $dbh->query($sql)->fetchAll(PDO::FETCH_ASSOC);
to this -
$result = $stmt2->fetchAll(PDO::FETCH_ASSOC);
You're trying to run the query again when all you need to do is grab the results.
The query you are running is against a prepared statement which is incorrect, removing the query and assigning the result from the execute statement will work. There's also quite a few other problems in your php. Also using the input directly from the user such as the table (which appears to be undefined in this code snippet), sort order and sort value leaves you open to sql injection.
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//get values from AJAX
$whereCategory = isset($_GET['cat_code'])? $_GET['cat_code'] : '';
$sortvalue = isset($_GET['sortvalue'])? $_GET['sortvalue'] : '';
$sortorder = isset($_GET['sortorder'])? $_GET['sortorder'] : '';
/** you cannot use prepared statements for doing the order by */
$sql = "select * from $table where cat_code = :cat_code order by $sortvalue $sortorder;";
$stmt2 = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY) );
$query = $stmt2->execute(['cat_code' => $whereCategory]);
/** below line isn't needed because above on deals with it */
//$dbh->query($sql)->fetchAll(PDO::FETCH_ASSOC);
$result = $query->fetchAll();
header('Content-type: application/json');
echo json_encode($result);
try without quotes:
$sql = "select * from {$table} where cat_code = :cat_code order by :sortvalue :sortorder;";
Use Like this
$result = $stmt2->fetchAll(PDO::FETCH_ASSOC);

PDO library with better parameter placement

Don't get me wrong PDO is great but what I don't like about it, is how variables are placed far away from the place they belong inside the SQL statement. Meaning I have a query like:
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = ? AND pass = ?");
The variables that are replacing the ? are always far away some lines below:
$stmt->bindParam(1, $email);
$stmt->bindParam(2, $pass);
If you have a lot of parameters this can get quite ugly. Using :email instead of ? does not make it much better. Sometimes you see the parameters as array in the same methode like:
$db->query("SELECT * FROM users WHERE email = ? AND pass = ?",
array($email, $pass));
A little bit better but with 10 variables it is still ugly. You practically create a variable ? you only use once. Also code highlighting is not supported for this pseudo variable.
I think it would be nicer to have it like this
$db->prepare("SELECT * FROM user WHERE email = ", $email, " AND pass = ", $pass);
You could even include the parameters of binParam() like this:
$db->prepare_new(
"SELECT * FROM user WHERE email = ", array($email, PDO::PARAM_STR),
" AND pass = ", $pass);
I wounder if there is a library that supports this type of style. Do you know one?
If always every even parameter will be parameter you can do it like this:
class MyPDO extends PDO {
public function prepareQuery() {
$query_str = "";
$params = array();
foreach(func_get_args() as $key => $param) {
if( $key % 2 == 0 ) {
$query_str .= $param;
}
else {
$params[] = $param;
$query_str .= ' ? ';
}
}
$prepared = $this->prepare($query_str);
foreach( $params as $key => $param ) {
$prepared->bindParam( $key+1, $param );
}
return $prepared;
}
}
and then you can use it as you wanted:
$db = new MyPDO( .. );
$db->prepareQuery("SELECT * FROM user WHERE email = ", $email, " AND pass = ", $pass);
PS: not tested - just a concept
A lot of the point of having ? and :email is that you can reuse the query multiple times. For example:
$stmt = $pdo->prepare("SELECT true FROM user WHERE email = :email");
$stmt->execute(array($email1));
$stmt->execute(array($email2));
// etc.
Having specific variables in the query removes this functionality entirely.
If you wanted, you could always extend or comprise PDO, though:
class DB {
private $pdo;
public function executeQuery($query, $args) {
$stmt = $this->pdo->prepare($query);
$stmt->execute($args);
}
}
$db->executeQuery("SELECT true FROM user WHERE email = :email", array($email1));
This hides the functionality of PDO that you dislike.
UPDATE:
This is an unusual way of doing things, but it seems to be what you're after:
$pdo->query("SELECT true FROM user WHERE email = " . $pdo->quote($email));
http://us2.php.net/manual/en/pdo.quote.php
You could put something similar to this into a function:
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = :email AND pass = :pass");
$arr = array(
'email' => 'test#test.com',
'pass' => 'secret'
);
foreach($arr as $key => $val){
$stmt->bindParam(':'.$key, $val);
}
Example:
function myBindParams($stmt, $bindings){
foreach($bindings as $key => $val){
$stmt->bindParam(':'.$key, $val);
}
return $stmt;
}
$stmt = $dbh->prepare("SELECT * FROM users WHERE email = :email AND pass = :pass");
$arr = array(
'email' => 'test#test.com',
'pass' => 'secret'
);
$stmt = myBindParams($stmt, $arr);

PDO insert query, why isn't this working

I stupidly built my web application with mysqli. Now, I'm trying to convert my data abstraction layer to pdo, but for some reason the insert query is giving me trouble. my shortcut insert function is called from the controller, and I was hoping to keep it in the name format with the table name and column/values array as the parameters.
I commented where I think the problem is below. Please help.
function insert($table, array $columns_values) {
// connect to db
$dbh = $this->db_connect();
$i = 0;
$columns = array();
$values = array();
$params = array();
foreach($columns_values as $column => $value) {
$i++;
$param = array($i => $value);
array_push($params, $param);
array_push($columns, $column);
array_push($values, '?');
}
// turn arrays into comma separated list
$columns = implode(",", $columns);
$values = implode(",", $values);
$stmt = $dbh->prepare("INSERT INTO $table ($columns) VALUES ($values)");
foreach ($params as $param_stmt) {
// i think this is where the problem is
foreach ($param_stmt as $placeholder => $value) {
$stmt->bindParam($placeholder, $value);
}
}
$stmt->execute();
return $stmt;
} // end insert()
I wouldn't do it your way. After a few minutes, I came up with this:
/**
* Function to insert a list of values to the database.
*
* #param PDO $pdo
* #param string $table
* #param array $columns_values
*
* #throws \Exception
* #throws \PDOException
*/
function insert_to_db(PDO $pdo, $table, array $columns_values) {
//Some data validation.
if (empty($columns_values)) {
throw new \Exception("Insert at least one value.");
}
if (empty($table)) {
throw new \Exception("Table may not be empty.");
}
//Implode all of column names. Will become the columns part of the query.
$str_columns = implode(", ", array_keys($columns_values));
//Implode all column names after adding a : at the beginning.
//They will become the placeholders on the values part.
$prepared_column_names = array_map(function ($el) {
return ":$el";
}, array_keys($columns_values));
$prepared_str_columns = implode(", ", $prepared_column_names);
//The query itself. Will look like "INSERT INTO `$table` (col1, col2, col3) VALUES (:col1, :col2, :col3);"
$query = "INSERT INTO `$table` ($str_columns) VALUES ($prepared_str_columns);";
//Prepare the query
$stmt = $pdo->prepare($query);
//Iterate over the columns and values, and bind the value to the placeholder
foreach ($columns_values as $column => $value) {
$stmt->bindValue(":$column", $value);
}
//Execute the query
$stmt->execute();
}
Things I changed
I don't instantiate the PDO object inside of the function. The function needs one in order to work, so it should be one of the arguments!
I throw Exceptions in case of an error. It's a better way of handling errors.
I use named placeholders instead of unnamed ones (:name vs ?). Produces more readable, easier to follow queries, should you ever need to debug.
Added comments to code. Again, you understand what you wrote now, but will you 6 months from now?
I made use of array_keys() to automatically generate an array full of keys (i.e. the columns), instead of looping and manually adding one.
Some tips
When you instantiate a PDO object, make sure it throws PDOExceptions on error! Like so:
new PDO($dsn, $user, $pass, array(PDO::PARAM_ERRMODE => PDO::ERRMODE_EXCEPTION));
or
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::PARAM_ERRMODE, PDO::ERRMODE_EXCEPTION);
That way, you don't need to explicitly check for errors each time, you use a single try catch block for the whole thing, and you're good:
try {
insert_to_db($pdo, $table, $array_of_columns_and_values);
}
catch (\Exception $e) { //Will catch all kinds of exceptions, including PDOExceptions
echo $e->getMessage();
}
You haven't checked that your prepare() actually succeeded:
$sql = "INSERT ....";
$stmt = $dbh->prepare($sql);
if (!$stmt) {
die($sql . $dbh->errorInfo());
}
Never assume a query succeeded, especially when you're building one totally dynamically as you are.
Without seeing what your original $columns_values array looks like.
Hope it helps
<?php
function insert($table, $values){
$dbh = $this->db_connect();
$fieldnames = array_keys($values[0]);
$sql = "INSERT INTO $table";
/*** set the field names ***/
$fields = '( ' . implode(' ,', $fieldnames) . ' )';
/*** set the placeholders ***/
$bound = '(:' . implode(', :', $fieldnames) . ' )';
/*** put the query together ***/
$sql .= $fields.' VALUES '.$bound;
//INSERT INTO testtable( id ,col1 ,col2 ) VALUES (:id, :col1, :col2 )
/*** prepare and execute ***/
$query = $dbh->prepare($sql);
foreach($values as $vals){
$query->execute($vals);
/* Array
(
[id] =
[col1] = someval1
[col2] = Someval21
)*/
}
}
//Multi Insert
$insert = array(array('id'=>'','col1'=>'someval1','col2'=>'Someval21'),
array('id'=>'','col1'=>'someval2','col2'=>'Someval22'),
array('id'=>'','col1'=>'someval3','col2'=>'Someval23'),
array('id'=>'','col1'=>'someval4','col2'=>'Someval24')
);
insert('testtable',$insert);
?>

Categories