PHP PDO custom sql query preparation - php

I'm having a little problem getting a sql query with prepare on PDO, I have this code:
$portfolio = $db->prepare("SELECT * FROM `news`, `:sub` WHERE `news`.`id` = `:sub`.`id_news` AND `page` = `:under` ORDER BY `date` DESC LIMIT :start, :limit");
$portfolio->bindParam(':under', $_GET['under'], PDO::PARAM_STR);
$portfolio->bindParam(':sub', $_GET['sub'], PDO::PARAM_STR);
$portfolio->bindParam(':start', $start, PDO::PARAM_INT);
$portfolio->bindParam(':limit', $limit, PDO::PARAM_INT);
$portfolio->execute();
But this doesn't give any value and my DB has the values correct, any one knows why this doesn't work?
PS: var $start and $limit are fine, no problem with it cuz it's pagination script, that work very fine in all the pages.
For exemple i'm in the url: mysite.com/index.php?sub=vid&under=info
so the query should be like this:
"SELECT * FROM `news`, `vid` WHERE `news`.`id` = `vid`.`id_news` AND `page` = `info` ORDER BY `date` DESC LIMIT 0, 10"
So for what i understood having this code before should work and still be safe right?
switch($_GET['sub']){
case "vid":
$table = "vid";
break;
case "img":
$table = "img";
break;
}
$portfolio = $db->prepare("SELECT * FROM `news`, `$table` WHERE `news`.`id` = `$table`.`id_news` AND `page` = :under ORDER BY `date` DESC LIMIT :start, :limit");

You can't use query parameter placeholders for table names or column names.
Use query parameters only to substitute for a literal value in an expression. I.e. a quoted string, quoted date, or numeric value.
Also, even if you are using a parameter for a string or date, the parameter doesn't go inside quotes.
To make table names or column names dynamic, you have to interpolate application variables into your SQL string before you submit the string to prepare().
But be careful to validate user input (e.g. $_GET variables) so you avoid SQL injection. For instance, test the input against a list of known legitimate table names.
Example:
$subtables = array(
"DEFAULT" => "text",
"text" => "text",
"vid" => "vid",
"pic" => "pic"
);
// if the key exists, use the table name, else use the default table name
$subtable = $subtables[ $_GET["sub"] ] ?: $subtables[ "DEFAULT" ];
// now $subtable is effectively whitelisted, and it is safe to use
// without risk of SQL injection
$portfolio = $db->prepare("SELECT *
FROM news, `$subtable` AS sub
WHERE news.id = sub.id_news
AND page = :under
ORDER BY date DESC LIMIT :start, :limit");

You can't use parameters for the names of tables and table object (i.e fields). See this question where this is covered.
Can PHP PDO Statements accept the table or column name as parameter?

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
are two magical lines that will solve all your minor problems after you fix the BIGGEST one - a dynamically linked table.
What it have to be is a single table where "sub" is a field name to distinguish a category
SELECT * FROM news n, subnews s
WHERE n.id = id_news AND s.sub =:sub AND `page` = :under
ORDER BY `date` DESC LIMIT :start, :limit
Also you have to quit that habit of wrapping in backticks everything that moves.

Related

cannot use prepared statements [duplicate]

I'm having problems using params in the ORDER BY section of my SQL. It doesn't issue any warnings, but prints out nothing.
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY :order :direction");
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_STR);
$stmt->bindParam(':direction', $direction, PDO::PARAM_STR);
$stmt->execute();
The :my_param works, but not :order or :direction. Is it not being internally escaped correctly? Am I stuck inserting it directly in the SQL? Like so:
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT * from table WHERE column = :my_param ORDER BY $order $direction");
Is there a PDO::PARAM_COLUMN_NAME constant or some equivalent?
Thanks!
Yes, you're stuck inserting it directly in the SQL. With some precautions, of course. Every operator/identifier must be hardcoded in your script, like this:
$orders=array("name","price","qty");
$key=array_search($_GET['sort'],$orders);
$order=$orders[$key];
$query="SELECT * from table WHERE is_live = :is_live ORDER BY $order";
Same for the direction.
I wrote a whitelisting helper function to be used in such cases, it greatly reduces the amount of code that needs to be written:
$order = white_list($order, ["name","price","qty"], "Invalid field name");
$direction = white_list($direction, ["ASC","DESC"], "Invalid ORDER BY direction");
$sql = "SELECT field from table WHERE column = ? ORDER BY $order $direction";
$stmt = $db->prepare($sql);
$stmt->execute([$is_live]);
The idea here is to check the value and raise an error in case it is not correct.
I don't think you can :
Use placeholders in an order by clause
Bind column names : you can only bind values -- or variables, and have their value injected in the prepared statement.
It's possible use prepared statements in ORDER BY clause, unfortunately you need pass the order of column insted of the name and is required set PDO_PARAM_INT with type.
In MySQL you can get the order of columns with this query:
SELECT column_name, ordinal_position FROM information_schema.columns
WHERE table_name = 'table' and table_schema = 'database'
PHP code:
$order = 2;
$stmt = $db->prepare("SELECT field from table WHERE column = :param ORDER BY :order DESC");
$stmt->bindParam(':param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_INT);
$stmt->execute();
I don't think you can get ASC/DESC as part of the prepared statement, but the column you can if you list them all in the sql query like so:
// Validate between 2 possible values:
$sortDir = isset($_GET['sortDir']) && $_GET['sortDir'] === 'ASC' ? 'ASC' : 'DESC';
$sql = "
...
order
by
case :orderByCol
when 'email' then email
when 'age' then age
else surname
end
$sortDir
";
$stmt = $db->prepare($sql);
$stmt->bindParam(':orderByCol', $someColumn);
$stmt->execute();
Since ASC/DESC is only two possible values, you can easily validate and select between them as hardcoded values using php code.
You could also make use of the ELT(FIELD(,,,,,),,,,,) functions for this, but then ordering will always be done as a string, even if the column is a numeric data type that should be sorted using numeric semantics / collation.
Unfortunely I guess you could not make it with prepared statements.
It would make it no cacheable since different columns may have values that could be sorted with special sorting strategies.
Create query by using standard escapes and execute it directly.
It is possible . You can use number instead of field name in the 'order by' clause. This is a number starting from 1 and is in the order of field names in the query. And you can concatenate a string in for ASC or DESC. For example
"Select col1,col2,col3 from tab1 order by ? " + strDesc + " limit 10,5".
strDesc=" ASC" / " DESC".
If I'm not entirely mistaken, Pascal is right.
The only binding possible in PDO is the binding of values, as you did with the ':my_param' parameter.
However, there's no harm done in:
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY ".$order ." ".$direction);
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->execute();
The only thing to take notice of would be the correct escaping of $order and $direction, but since you set them manually and didn't set them via user input, I think you're all set.

How to pass parameters to a dynamic query?

I have a query which is not constant all the time ..., In fact it will be generate according to some factor. And it is sometimes like this:
select * from table1 where id = :id
And sometimes else it could be like this:
select * from table1 where id = :id
union all
select * from table2 where id = :id
And sometimes else it maybe be like this:
select * from table1 where id = :id
union all
select * from table2 where id = :id
union all
select * from table3 where id = :id
Already: I used PHP version 5.2.6 so far and it was completely fine. I mean is, I just passed one time :id parameter and I could use it for multiple times in the query. Just this:
$stm->bindValue(':id', $id, PDO::PARAM_INT);
This ^ was good for all those above queries.
Now: I have updated my PHP version (my current PHP version is 5.6.8). Well, As you know, in the new version, I cannot do that like former. I need to pass a separated parameter for every time that I want to use it in the query. like this:
For query1:
$stm->bindValue(':id', $id, PDO::PARAM_INT);
For query2:
$stm->bindValue(':id1', $id, PDO::PARAM_INT);
$stm->bindValue(':id2', $id, PDO::PARAM_INT);
For query3:
$stm->bindValue(':id1', $id, PDO::PARAM_INT);
$stm->bindValue(':id2', $id, PDO::PARAM_INT);
$stm->bindValue(':id3', $id, PDO::PARAM_INT);
So given that I don't know how many times I need to pass :id parameter to the query (because my query is dynamic), How can I solve this problem?
You could store all id values in an array and iterate over it.
This will work as long as all values are of same type (int) and your place holders are of the form :idNUMBER:
$ids = array('what','ever');
for($c=0;$c<count($ids);$c++) {
$stm->bindValue(':id' . ($c+1), $ids[$c], PDO::PARAM_INT);
}
Not very elegant but will do the job.
A solution to even handle different types is described at http://www.pontikis.net/blog/dynamically-bind_param-array-mysqli

PDO adds the apostrophe to the mySQL query

After years of reading it's time to ask first question :)
My problem is that after migrating the code from mySQLi to PDO we have got a problem as it seems PDO adds the apostrophes to the query.
PHP code goes like that:
$sort = $_GET['sort']; << table column name (mySQL VARCHAR only columns)
....
$query = 'SELECT * FROM table WHERE xxx > 0';
$query .= ' ORDER BY :sort ASC ;';
$qry_result= $db->prepare($query);
$qry_result->execute(array(':sort'=>$sort));
mysqli version went smoothly but now queries (mysql log file) looks like this:
SELECT * FROM table where xxx > 0 ORDER BY 'SORT_VAR_VALUE' ASC;
^ 2 problems ^
So the table is NOT sorted, as sort order (from mySQL point of view) is wrong.
phpinfo() does not get any results for search on "magic" nor "quotes" btw.
Any idea ??
The placeholders in PDO statements are for values only. If you want to add actual SQL to the query you need to do it another way.
First, you should sanitize $sort and surround it with backticks in the query.
$sort = preg_replace('/^[a-zA-Z0-9_]/', '', $sort);
Then you could double quote the query string and PHP will replace $sort with it's value for you:
$query = "SELECT * FROM table WHERE xxx > 0 ORDER BY `$sort` ASC";
Or you could replace it with preg_replace like so:
$query = 'SELECT * FROM table WHERE xxx > 0 ORDER BY `:sort` ASC';
$query = preg_replace('/:sort/', $sort, $query, 1);
I would use the preg_replace method because it allows you to reuse the query if you assign the results from preg_replace to another variable instead of overwriting the original variable.
by default pdo binds values as strings.
To fix this you will want to check that the column is actually a valid name and then add it to the query, you can do it the following way:
function validName($string){
return !preg_match("/[^a-zA-Z0-9\$_\.]/i", $string);
}
if(validName($sort)){
$db->prepare("SELECT * FROM table where xxx > 0 ORDER BY $sort ASC");
}
With PDO it's not possible to bind other things that variables in the WHERE statement. So you have to hard code the names of the columns you order by.
See How do I set ORDER BY params using prepared PDO statement?
or Can PHP PDO Statements accept the table or column name as parameter? for further explanations.

PDO bindParam not working as expected

This has been bugging for a long time, and I still can't figure out what i'm doing wrong.
In the code I want to select a few users with a comma separated string. The string will always be legit and valid.
In the first example, the one I would like to use, uses bindParam to assign the value of $postId to the SQL query. I have been using bindParam() for lots of other calls, but in this specific case, it fails.
$postId = "1,2,3";
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, "?") ORDER BY id DESC');
$stm->bindParam(1, $postId, PDO::PARAM_STR);
$stm->setFetchMode(PDO::FETCH_ASSOC);
$stm->execute();
$results = $stm->fetchAll();
return print_r($results,true);
This code returns:
array (
)
In this other code which I really wouldn't like to use, I just pass the value of $postId right into the sql query.
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, "'.$postId.'") ORDER BY id DESC');
$stm->setFetchMode(PDO::FETCH_ASSOC);
$stm->execute();
$results = $stm->fetchAll();
return print_r($results,true);
This code returns all the rows it is supposed to retrieve.
My question is; What is the specific problem and how can I avoid doing this again?
You shouldn't have quotes around the placeholder in yout query:
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, ?) ORDER BY id DESC');
See additional docs here.
Although it's not directly related to the question, it's also a handy habit to get into to use named params. When you have only one param to pass, it's not too bad, but when you start getting five or so question marks in the query, it's MUCH easier to actually read if you used named params:
SELECT * FROM posts WHERE find_in_set(userId, :someID) ORDER BY id DESC
Then you bind them as named params in your code:
$sth->bindParam(':someID', $postId, PDO::PARAM_STR);
You don't need to add the double quotes "?" when referencing the value
'SELECT * FROM posts WHERE find_in_set(userId, "?") ORDER BY id DESC'
Should be
'SELECT * FROM posts WHERE find_in_set(userId, ?) ORDER BY id DESC'

assign integers as variables

Generic topic I know. Its hard to be specific in a topic. Anyway I have problem with this sql logic:
"SELECT * FROM imagecomment WHERE filename = :filename ORDER BY comment_timestamp DESC LIMIT '$min', '$max'";
When I try the same logic but with integers directly in the sql logic it works. The variables min and max are integers:
int(10)
int(20) I get this when var_dump() them. I also tried to bindValue() the variables (as I usually do) to two parameters but it still didn't work.
You're using this in a PDOStatement, right? (since you're using :filename)
$db = new PDO(); //assume you have this set
$stmt = $db->prepare(
"SELECT *
FROM imagecomment
WHERE filename = :filename
ORDER BY comment_timestamp
DESC LIMIT :min, :max";
$stmt->bindValue(':filename', $filename);
$stmt->bindValue(':min', $min);
$stmt->bindValue(':max', $max);
$stmt->execute();
And you should be able to fetch your results, If that doesn't work, let me know what error you're getting back from the PDOStatement.
"SELECT * FROM imagecomment
WHERE filename = :filename ORDER BY comment_timestamp DESC LIMIT $min, $max";

Categories