I need to change this query to use a prepared statement. Is it possible?
The query:
$sql = "SELECT id, title, content, priority, date, delivery FROM tasks " . $op . " " . $title . " " . $content . " " . $priority . " " . $date . " " . $delivery . " ORDER BY " . $orderField . " " . $order . " " . $pagination . "";
Before the query, there's code to check the POST variables and change the content of variables in the query.
//For $op makes an INNER JOIN with or without IN clause depending on the content of a $_POST variable
$op = "INNER JOIN ... WHERE opID IN ('"$.opID."')";
//Or
$op = "INNER JOIN ... ";
//For $title (depends of $op):
$title = "WHERE title LIKE'%".$_POST["title"]."%'";
//Or
$title = "AND title LIKE'%".$_POST["title"]."%'";
//For $content:
$content = "AND content LIKE '%".$_POST["content"]."%'";
//For $priority just a switch:
$priority = "AND priority = DEPENDING_CASE";
//For $date and $delivery another switch
$d = date("Y-m-d", strtotime($_POST["date"]));
$date = "AND date >= '$d' 00:00:00 AND date <= '$d' 23:59:59";
//Or $date = "AND date >= '$d' 00:00:00";
//Or $date = "AND date <= '$d' 23:59:59";
//For $orderField
$orderField = $_POST["column"];
//For $order
$order= $_POST["order"];
//For $pagination
$pagination = "LIMIT ".$offset.",". $recordsPerPage;
How I could do this query using prepared statement?
The query could be more static but this means to make different prepared statements and execute it depending of $_POST checks.
It depends on many variables because this query show results in a table that contains search fields and column to order.
A full example of query would be like this (depending of $_POST checks):
SELECT id, title, content, priority, date, delivery FROM tasks INNER JOIN op ON task.op = op.opId WHERE op IN (4851,8965,78562) AND title LIKE '%PHT%' AND content LIKE '%%' AND priority = '2' ORDER BY date DESC LIMIT 0, 10
An excellent question. And thank you for moving to prepared statements. It seems that after all those years of struggle, the idea finally is starting to take over.
Disclaimer: there will be links to my own site because I am helping people with PHP for 20+ years and got an obsession with writing articles about most common issues.
Yes, it's perfectly possible. Check out my article, How to create a search filter for mysqli for the fully functional example.
For the WHERE part, all you need is to create two separate arrays - one containing query conditions with placeholders and one containing actual values for these placeholders, i.e:
WHERE clause
$conditions = [];
$parameters = [];
if (!empty($_POST["content"])) {
$conditions[] = 'content LIKE ?';
$parameters[] = '%'.$_POST['content ']."%";
}
and so on, for all search conditions.
Then you could implode all the conditions using AND string as a glue, and get a first-class WHERE clause:
if ($conditions)
{
$where .= " WHERE ".implode(" AND ", $conditions);
}
The routine is the same for all search conditions, but it will be a bit different for the IN() clause.
IN() clause
is a bit different as you will need more placeholders and more values to be added:
if (!empty($_POST["opID"])) {
$in = str_repeat('?,', count($array) - 1) . '?';
$conditions[] = "opID IN ($in)";
$parameters = array_merge($parameters, $_POST["opID"]);
}
this code will add as many ? placeholders to the IN() clause as many elements in the $_POST["opID"] and will add all those values to the $parameters array. The explanation can be found in the adjacent article in the same section on my site.
After you are done with WHERE clause, you can move to the rest of your query
ORDER BY clause
You cannot parameterize the order by clause, because field names and SQL keywords cannot be represented by a placeholder. And to tackle with this problem I beg you to use a whitelisting function I wrote for this exact purpose. With it you can make your ORDER BY clause 100% safe but perfectly flexible. All you need is to predefine an array with field names allowed in the order by clause:
$sortColumns = ["title","content","priority"]; // add your own
and then get safe values using this handy function:
$orderField = white_list($_POST["column"], $sortColumns, "Invalid column name");
$order = white_list($_POST["order"], ["ASC","DESC"], "Invalid ORDER BY direction");
this is a smart function, that covers three different scenarios
in case no values were provided (i.e. $_POST["column"] is empty) the first value from the white list will be used, so it serves as a default value
in case a correct value provided, it will be used in the query
in case an incorrect value is provided, then an error will be thrown.
LIMIT clause
LIMIT values are perfectly parameterized so you can just add them to the $parameters array:
$limit = "LIMIT ?, ?";
$parameters[] = $offset;
$parameters[] = $recordsPerPage;
The final assembly
In the end, your query will be something like this
$sql = "SELECT id, title, content, priority, date, delivery
FROM tasks INNER JOIN ... $where ORDER BY `$orderField` $order $limit";
And it can be executed using the following code
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters);
$stmt->execute();
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
where $data is a conventional array contains all the rows returned by the query.
Related
I need to change this query to use a prepared statement. Is it possible?
The query:
$sql = "SELECT id, title, content, priority, date, delivery FROM tasks " . $op . " " . $title . " " . $content . " " . $priority . " " . $date . " " . $delivery . " ORDER BY " . $orderField . " " . $order . " " . $pagination . "";
Before the query, there's code to check the POST variables and change the content of variables in the query.
//For $op makes an INNER JOIN with or without IN clause depending on the content of a $_POST variable
$op = "INNER JOIN ... WHERE opID IN ('"$.opID."')";
//Or
$op = "INNER JOIN ... ";
//For $title (depends of $op):
$title = "WHERE title LIKE'%".$_POST["title"]."%'";
//Or
$title = "AND title LIKE'%".$_POST["title"]."%'";
//For $content:
$content = "AND content LIKE '%".$_POST["content"]."%'";
//For $priority just a switch:
$priority = "AND priority = DEPENDING_CASE";
//For $date and $delivery another switch
$d = date("Y-m-d", strtotime($_POST["date"]));
$date = "AND date >= '$d' 00:00:00 AND date <= '$d' 23:59:59";
//Or $date = "AND date >= '$d' 00:00:00";
//Or $date = "AND date <= '$d' 23:59:59";
//For $orderField
$orderField = $_POST["column"];
//For $order
$order= $_POST["order"];
//For $pagination
$pagination = "LIMIT ".$offset.",". $recordsPerPage;
How I could do this query using prepared statement?
The query could be more static but this means to make different prepared statements and execute it depending of $_POST checks.
It depends on many variables because this query show results in a table that contains search fields and column to order.
A full example of query would be like this (depending of $_POST checks):
SELECT id, title, content, priority, date, delivery FROM tasks INNER JOIN op ON task.op = op.opId WHERE op IN (4851,8965,78562) AND title LIKE '%PHT%' AND content LIKE '%%' AND priority = '2' ORDER BY date DESC LIMIT 0, 10
An excellent question. And thank you for moving to prepared statements. It seems that after all those years of struggle, the idea finally is starting to take over.
Disclaimer: there will be links to my own site because I am helping people with PHP for 20+ years and got an obsession with writing articles about most common issues.
Yes, it's perfectly possible. Check out my article, How to create a search filter for mysqli for the fully functional example.
For the WHERE part, all you need is to create two separate arrays - one containing query conditions with placeholders and one containing actual values for these placeholders, i.e:
WHERE clause
$conditions = [];
$parameters = [];
if (!empty($_POST["content"])) {
$conditions[] = 'content LIKE ?';
$parameters[] = '%'.$_POST['content ']."%";
}
and so on, for all search conditions.
Then you could implode all the conditions using AND string as a glue, and get a first-class WHERE clause:
if ($conditions)
{
$where .= " WHERE ".implode(" AND ", $conditions);
}
The routine is the same for all search conditions, but it will be a bit different for the IN() clause.
IN() clause
is a bit different as you will need more placeholders and more values to be added:
if (!empty($_POST["opID"])) {
$in = str_repeat('?,', count($array) - 1) . '?';
$conditions[] = "opID IN ($in)";
$parameters = array_merge($parameters, $_POST["opID"]);
}
this code will add as many ? placeholders to the IN() clause as many elements in the $_POST["opID"] and will add all those values to the $parameters array. The explanation can be found in the adjacent article in the same section on my site.
After you are done with WHERE clause, you can move to the rest of your query
ORDER BY clause
You cannot parameterize the order by clause, because field names and SQL keywords cannot be represented by a placeholder. And to tackle with this problem I beg you to use a whitelisting function I wrote for this exact purpose. With it you can make your ORDER BY clause 100% safe but perfectly flexible. All you need is to predefine an array with field names allowed in the order by clause:
$sortColumns = ["title","content","priority"]; // add your own
and then get safe values using this handy function:
$orderField = white_list($_POST["column"], $sortColumns, "Invalid column name");
$order = white_list($_POST["order"], ["ASC","DESC"], "Invalid ORDER BY direction");
this is a smart function, that covers three different scenarios
in case no values were provided (i.e. $_POST["column"] is empty) the first value from the white list will be used, so it serves as a default value
in case a correct value provided, it will be used in the query
in case an incorrect value is provided, then an error will be thrown.
LIMIT clause
LIMIT values are perfectly parameterized so you can just add them to the $parameters array:
$limit = "LIMIT ?, ?";
$parameters[] = $offset;
$parameters[] = $recordsPerPage;
The final assembly
In the end, your query will be something like this
$sql = "SELECT id, title, content, priority, date, delivery
FROM tasks INNER JOIN ... $where ORDER BY `$orderField` $order $limit";
And it can be executed using the following code
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters);
$stmt->execute();
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
where $data is a conventional array contains all the rows returned by the query.
The following is the PHP code I am using for a simple search feature in my website.
The search simply shows refults if it matches the SQL column "tags".
I would like to add one more filter in the SQL query.
I want to filter the search results based on city.
The city data is already in the SQL, but I dont know how to add it here without breaking the properly working search funtion.
I tried $data_sql .= " AND city='newyork' "; after the 8th line, but it didnt work.
$name=str_replace(' ', '%', $_POST['query']);
$newsearch = "%$name%";
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ?";
$count_sql = sprintf($base_sql, "count(*)");
$stmt = $connect->prepare($count_sql);
$stmt->execute([$newsearch]);
$total_data = $stmt->fetchColumn();
$data_sql = $count_sql = sprintf($base_sql, "*")." LIMIT ?,?";
$stmt = $connect->prepare($data_sql);
$stmt->execute([$newsearch, $start, $limit]);
$result = $stmt->fetchAll();
So your additional filter must be before LIMIT ?, ?
if you try adding it after the 8th line the query will look like this:
SELECT * FROM posts WHERE tags LIKE 'search' LIMIT 0, 100 AND city='newyork'
so what can you do:
$data_sql = sprintf($base_sql, "*");//we will add the limit before preparation
//don't know why do you need that $count_sql here
$data_sql .= " AND city='newyork' ";
//IF you need some GROUP BY do it here
//If you need some ORDER BY do it here
$data_sql .= " LIMIT ?, ?";
$stmt = $connect->prepare($data_sql);
$stmt->execute([$newsearch, $start, $limit]);
$result = $stmt->fetchAll();
The line $data_sql .= " AND city='newyork' "; won't work as it will add the string after the LIMIT which is not a valid sql query.
You should instead edit the line with the base_sql like this:
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ? AND city='newyork'";
And of course if 'newyork' needs to be a variable you can do thr same thing like you did for the tags
First, let's add the new criteria:
$base_sql = "SELECT %s FROM posts WHERE tags LIKE ? and city = ?";
Then make sure that you pass the city as a parameter
$stmt->execute([$newsearch, 'newyork', $start, $limit]);
I am using PDO in php. But when my query have any keyword like " ' " means hyphen it breaks and through an error.
I ready through on internet and find solution to bind parameters with query and it works fine.
But the issue is i am building the query in loop and i am not able to bind parameters within loop.
Here is code in which i am splitting the array with space and run query on every keyword.
The first 3 words will have only like query and more then 3 words i am using loop to concatenate the all array elements and same with more then 6 words i am using MATCH query.
Is there any way to escape that hyphen or how can we bind parameters using loop in my case?
$keyword = ($_POST['keyword']);
$keyword_array = split(' ',$keyword);
/* Query For first Three Words */
if(count($keyword_array)<=3){
$sql = "SELECT * FROM faq WHERE question LIKE '%$keyword%' limit 14";
}
/* Query through all array when words are greater then 3 */
if(count($keyword_array)< 6){
$sql = "SELECT * FROM faq WHERE question ";
for($i = 0 ; $i<count($keyword_array); $i++){
if($i==0){
$sql.=" LIKE '%$keyword_array[$i]%'";
}else{
$sql.=" or question LIKE '%$keyword_array[$i]%' ";
}
}
$sql .= " ORDER BY question ASC LIMIT 0, 8";
}
/* Appl FULL TEXT in natual language mode once we have enough phrase */
else if(count($keyword_array)>=6){
$sql = "SELECT * FROM faq WHERE ";
for($i = 0 ; $i<count($keyword_array); $i++){
if($i==0){
$sql.=" MATCH (answer) AGAINST ('$keyword_array[$i]' in natural language mode) ";
}else{
$sql.=" or MATCH(answer) AGAINST('$keyword_array[$i]' in natural language mode) ";
}
}
$sql .= " limit 0,5";
}
$execute_faq_query = $conn->query($sql);
$execute_faq_query->setFetchMode(PDO::FETCH_ASSOC);
while ($list = $execute_faq_query->fetch()){
}
When building a dynamic query you need to separate those parts of the query that are static from those that are dynamic.
You can see that the following code is static.
"SELECT * FROM faq ";
The rest of the code is dynamic. When filtering records the WHERE clause is used and the AND & OR operators are used to filter records based on more than one condition. The AND operator displays a record if both the first condition AND the second condition are true. The OR operator displays a record if either the first condition OR the second condition is true. so for the first condition WHERE is used but after that AND or OR must be used(using OR in your example)
// Static code
sql = "SELECT * FROM `faq`"
// Set initial condition to WHERE
clause = "WHERE";
if( !empty( filter )){
Add clause to sql
Add condition to sql
change clause to OR or AND as required
}
Repeat for each filter
Note the filter is not changed until a filter is applied and remains changed once changed.
The remaining static code, if any,is added after all the filters have been handled.
I have used Switch Case to apply filters and unnamed parameters ?.
Use "lazy" binding when possible - passing data into execute will dramatically shorten your code. See PDO info.
//Test $POST[] remove after testing
$_POST['keyword'] ="one two three four five six";
$keyword = ($_POST['keyword']);
$keyword_array = split(' ',$keyword);
$words = count($keyword_array);
echo $words;
//You need an array to store parameters
$paramArray =array();
//Initial clause
$clause = "WHERE";
//Start with a basic stub
$sql = "SELECT * FROM faq ";
switch (true) {
case $words <= 3:
$sql .= " $clause question LIKE ?";
$keyword = "%$keyword%";
array_push($paramArray,$keyword);
$limit = " LIMIT 14";
break;
case $words < 6:
for($i = 0 ; $i<count($keyword_array); $i++){
$sql .= " $clause question LIKE ?";
$keyword = "%$keyword_array[$i]%";
array_push($paramArray,$keyword);
$clause = "OR";
$limit = " ORDER BY question ASC LIMIT 0, 8";
}
break;
case $words >=6:
$clause = "";
for($i = 0 ; $i<count($keyword_array); $i++){
$sql.=" $clause MATCH (answer) AGAINST (? in natural language mode) ";
array_push($paramArray,$keyword_array[$i]);
$clause = "OR";
$limit = " limit 0,5";
}
break;
}
//echo query and parameter array remove after testing
echo $sql;
echo "<br>";
print_r($paramArray);
//Prepare and execute query
$execute_faq_query = $conn->prepare($sql);
$execute_faq_query->execute($paramArray);
$execute_faq_query->setFetchMode(PDO::FETCH_ASSOC);
while ($list = $execute_faq_query->fetch()){
}
I have a a page that appends different parameters to the URL that are used for the query.
For example
http://www.example.com/search.php?category=Schools&country[]=Belgium&country[]=Czech+Republic
My code is like this
if(isset($_GET['country'])){
$cties = "'" . implode("','", $_GET['country']) . "'";
}
else {
$cties = "'Albania','Andorra','Austria','Belarus','Belgium','Bosnia & Herzegovina','Bulgaria','Croatia','Czech Republic','Denmark','Estonia','Faroe Islands','Finland','France','Germany','Gibraltar','Great Britain','Greece','Hungary','Iceland','Ireland','Isle of Man','Italy','Latvia','Liechtenstein','Lithuania','Luxembourg','Macedonia','Malta','Moldova','Monaco','Montenegro','Netherlands','Norway','Poland','Portugal','Serbia','Romania','San Marino','Slovakia','Slovenia','Spain','Sweden','Switzerland','Ukraine','United Kingdom'";
}
if(isset($_GET['category'])){
$cat = $_GET['category'];
}
else{
$cat = " ";
}
try{
// create the Prepared Statement
$stmt = $con->prepare("SELECT * FROM MyTable
WHERE MyDate >= DATE(NOW())
AND (Category=:cat or '' = :cat)
AND Country IN ($cties)
ORDER BY MyDate ASC");
$stmt->bindValue(':cat', $cat, PDO::PARAM_STR);
$stmt->execute();
I was wondering if this query is secure and if not, what I am doing wrong.
Thanks in advance!
I finally got it (thanks to Your Common Sense):
if(isset($_GET['country'])){
$arr = $_GET['country'];
}
else {
$arr = array('Albania','Andorra','Austria','Belarus','Belgium','Bosnia & Herzegovina','Bulgaria','Croatia','Czech Republic','Denmark','Estonia','Faroe Islands','Finland','France','Germany','Gibraltar','Great Britain','Greece','Hungary','Iceland','Ireland','Isle of Man','Italy','Latvia','Liechtenstein','Lithuania','Luxembourg','Macedonia','Malta','Moldova','Monaco','Montenegro','Netherlands','Norway','Poland','Portugal','Serbia','Romania','San Marino','Slovakia','Slovenia','Spain','Sweden','Switzerland','Ukraine','United Kingdom');
}
if(isset($_GET['category'])){
$cat = $_GET['category'];
}
else{
$cat = " ";
}
// create the Prepared Statement
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM MyTable WHERE MyDate >= DATE(NOW())
AND Country IN ($in)
AND (Category=? or '' = ?)
ORDER BY MyDate ASC";
$stmt = $con->prepare($sql);
$arr[] = $cat; // adding category to array
$arr[] = $cat; // we need it twice here
// finally - execute
$stmt->execute($arr);
Yeah, Now I see your problem. Well, PDO is not too convenient a library for such a task. So, first of all I'll show you how it can be done with my own library:
$sql = "SELECT * FROM MyTable WHERE MyDate >= CURDATE()
AND (Category=?s or '' = ?s)
AND Country IN (?a)
ORDER BY MyDate ASC"
$data = $db->getAll($sql, $cat, $cat, $_GET['country']);
But I quite realize that you all so inclined to familiar methods. Well, let's elaborate with ugly PDO
First of all, what is the goal? The goal is
to create the query that contains placeholders for all the data. I'll stick to positional placeholders as they are easier to implement.
To create an array with all the variables that have to be bound to placeholders
It seems we need two placeholders for category and some unknown number fro cities. All right, this line will create a string of placeholders:
$in = str_repeat('?,', count($arr) - 1) . '?';
which we are going to insert into query.
// $arr is array with all the vars to bind. at the moment it contains cities only
$arr = $_GET['country'];
// creating string of ?s
$in = str_repeat('?,', count($arr) - 1) . '?';
// building query
$sql = "SELECT * FROM MyTable WHERE MyDate >= DATE(NOW())
AND Country IN ($in)
AND (Category=? or '' = ?)
ORDER BY MyDate ASC";
$stm = $db->prepare($sql);
$arr[] = $_GET['category']; // adding category to array
$arr[] = $_GET['category']; // we need it twice here
// finally - execute
$stm->execute($arr);
$data = $stm->fetchAll();
No, the SQL code could be injected in the $_GET['country'] parameter. You don't escape it anywhere.
See PHP PDO: Can I bind an array to an IN() condition?
I know this has been asked 1000 times, but for some reason I continue to bang my head agains the wall..
This works:
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
$sql = $sql . 'WHERE a.regGUID in ( ' . $regGUID . ' ) and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = "' . $game . '" order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
$results = $stmt->execute();
This Doesn't:
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
$sql = $sql . 'WHERE a.regGUID in ( :regGUID ) and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = :game order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
$stmt->bindValue(':regGUID', $regGUID, PDO::PARAM_STR);
$stmt->bindValue(':game', $game, PDO::PARAM_STR);
$results = $stmt->execute();
What am I missing? Thanks
The problem is here:
$sql = $sql . 'WHERE a.regGUID in ( :regGUID ) and ';
$stmt->bindValue(':regGUID', $regGUID, PDO::PARAM_STR);
I assume $regGUID is a comma-separated list of quoted strings.
Each query parameter accepts only a single scalar value. Not lists of values.
So you have two choices:
Continue to interpolate the $regGUID string, even if you use parameters for other scalar values. But you still want to be careful to avoid SQL injection, so you must form the $regGUID string correctly. You can't just call PDO::quote() on the whole string, that would make it a single quoted string containing UUIDs and commas. You have to make sure each UUID string is escaped and quoted individually, then implode the list together and interpolate it into the IN clause.
$regGUIDs = explode(',', $regGUID);
$regGUIDs = array_map(function ($g) { return $db->quote($g); }, $regGUIDs);
$regGUID = implode(',', $regGUIDs);
$sql = $sql . 'WHERE a.regGUID in (' . $regGUID . ') and ';
explode() the $regGUID into an array, and add one query parameter for each element in the array. Interpolate the dynamic list of query parameter placeholders.
$regGUIDs = explode(',', $regGUID);
$params = array_fill(1, count($regGUIDs), '?');
$sql = $sql . ' WHERE a.regGUID in ( ' . implode(',', $params) . ' ) and ';
You could bindValue() in a loop for the array, but keep in mind that other parameters should also be bound by position, not by name. PDO has bugs that make it not happy when you try to mix the two different styles of parameters in the same query.
Instead of using bindValue() I just pass an array of parameter values to PDOStatement::execute(), which is much easier.
$paramValues = $regGUIDs;
$paramValues[] = $game;
$results = $stmt->execute($paramValues);
This indeed has been asked 1000 times.
Prepared statements can only accept scalar values, not arbitrary parts of the SQL query.
You have to form IN() statement using as many placeholders, as many items you have to put in and then bind them one by one.
To ease this task one can use some helper function.
Say, using SafeMysql library this code could be written as
$sql = 'SELECT * FROM events a, players b WHERE regGUID in (?a) and';
$sql .= ' a.playerCode=b.playerCode and a.gameCode = ?s';
$sql .= ' order by a.eventTime desc, a.actionCode asc';
$results = $db->getAll($sql,$regGUID,$game);
Note that $regGUID should be an array, not string and $results already contain all the requested data, without any further processing.
What is the content of $regGUID? Since you're using an in clause, I suspect a comma separated list.
Binding a variable to a parameter is not like substituting that string into the query; it is like telling MySQL how to use your actual PHP variables. So if you bind a string like '1,2,3' to the query parameter, it stays as one string, and is not reinterpreted as a list of numbers.
Consequently, if $regGUID is something like "'AAA1', 'BBB2'", your first query becomes
... WHERE a.regGUID in ( 'AAA1', 'BBB2' ) ...
but your second query is more like
... WHERE a.regGUID in ( '\'AAA1\', \'BBB2\'' ) ...
which is the same as saying
... WHERE a.regGUID = '\'AAA1\', \'BBB2\'' ...
As others have state you can only bind a single scalar value to a placeholder. So this means you actually need a placeholder for each value in your IN statement. I normally do something like the following. It should be noted though that i never use bindValue so if it has rules about things having to be references like Mysqli then the below may need to be modified:
$regGUIDPlaceholders = array();
// prepare the placeholders
// assume regGUID is an array - if its a string then explode on whatever to make it an array
foreach($regGUID as $k => $v) {
$placeholder = ':regGUID' . $k;
$regGUIDPlaceholders[$key] = $value;
}
// prepare the IN statememnt
$in = sprintf('IN (%s)', implode(',', array_keys($regGUIDPlaceholders)));
$sql = 'SELECT a.eventCode, a.eventTime, a.teamCode, a.playerCode, b.lastName, b.firstName, b.number, a.xCoord, a.yCoord, a.id ';
$sql = $sql . 'FROM events a, players b ';
// USE the IN statement dynamically prepared above
$sql = $sql . 'WHERE a.regGUID '. $in . ' and ';
$sql = $sql . 'a.playerCode=b.playerCode and a.gameCode = :game order by a.eventTime desc, a.actionCode asc';
$stmt = $db->prepare($sql);
// bind each GUID to its placeholder
foreach($regGUIDPlaceholders as $placeholder => $value) {
$stmt->bindValue($placeholder, $value, PDO::PARAM_STR);
}
$stmt->bindValue(':game', $game, PDO::PARAM_STR);
$results = $stmt->execute();