PDO bindParam not working as expected - php

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'

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 parameterized query not returning anything

I am converting old mysql_query code to PDO parameterized queries. Here's what I have so far. It doesn't seem to return anything. I have tried the same query in phpmyadmin, and in the old code with the same input, and the query returns rows those ways.
public function searchArticle($input)
{
$db = new PDO("mysql:host=localhost;dbname=thecorrectdbname", "root", "supersecretpassword");
$statement = $db->prepare("SELECT * FROM news WHERE headline LIKE '%:title%'
OR content LIKE %:content%'
OR author LIKE '%:author%'
ORDER BY id DESC");
$statement->execute(array('title' =>$query,
'content' =>$query,
'author'=>$query));
$result = $statement->fetchAll();
print_r($result);
if (!$result || $statement->rowCount() <= 0)
{
echo'nothing in this array';
return false;
}
return $result;
}
This returns
Array ( ) nothing in this array
Using the same $db connection I can manage to INSERT data into the DB, so the connection is working.
Two questions.
What am I doing wrong in this code?
Suppose I would get the code working. Is the $result object returned by a PDO prepared statement structurally the same as a mysql_query $result object? If not, how do I convert a PDO resultset to a mysql_query one?
Your replacement variables will get escaped and quoted automatically by PDO, which means you cannot have a variable within quotes.
change the following:
$statement = $db->prepare("SELECT * FROM news WHERE headline LIKE :title
OR content LIKE :content
OR author LIKE :author
ORDER BY id DESC");
$statement->execute(array('title' =>'%'.$query.'%',
'content' =>'%'.$query.'%',
'author'=>'%'.$query.'%'));
You are doing an invalid use of placeholder. Placeholder must be used in the place of whole value.
$statement = $db->prepare("SELECT * FROM news WHERE headline LIKE :title
OR content LIKE :content
OR author LIKE :author
ORDER BY id DESC");
$statement->execute(array('title' =>"%$query%",
'content' =>"%$query%",
'author'=>"%$query%"));

PHP PDO custom sql query preparation

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.

selecting mysql data inside an array with PDO fetchAll

I'm trying to get a certain data from mysql with fetchAll.
I currently have this:
$sql1= $dbh->query("SELECT * FROM information");
$comments= $dbh->prepare("SELECT * FROM comments WHERE idinformation= ?");
if($retail){
while($row = $retail -> fetch(PDO::FETCH_ASSOC)){
$comments->execute(array($row['idproducts']));
$json['data'][] = array(
'id'=>$row['idproducts'],
"headline"=>$row['headline'],
'price'=> array ("full_price" => $row['full_price']),
'description'=>$row['description'],
"comments" => $comments->fetchAll(PDO::FETCH_ASSOC)
);
}
$json_encode = json_encode($json);
$obj = json_decode($json_encode,true);
}
My comments is output all the columns in my database. What if I only want comments,time,id,etc... how would I distinguish this in this syntax?
Thanks in advance!
When querying a SQL database, SELECT statements should also include a list of columns you want returned. This not only saves time because the SQL database doesn't have to retrieve a list of columns on each query, but it requires you to think about what you need. In your case, if you only want: comments, time, id then you should specify it:
$comments= $dbh->prepare("SELECT id, time FROM comments WHERE idinformation= ?");
Change the line
$comments= $dbh->prepare("SELECT * FROM comments WHERE idinformation= ?");
to
$comments= $dbh->prepare("SELECT comments,time,id FROM comments WHERE idinformation= ?");
Edit:
Alternatively use group_concat(comments time id, ', ') in a single SQL query. You could do this as a single "JOIN"ed query, and group_concat the comments if you can get the separator you need. Everything is then done SQL-side = faster.
Documentation: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

Categories