PHP PDO MySQL count() prepared statement - php

I'm on a Web development course. Working with PHP PDO MySQL they teach us on a workshop to do this:
function countUsers($search) {
$and = '';
if ($search != '') {
$and = " AND user_name LIKE '%".$search."%'";
}
$total = $this->db->query("SELECT COUNT(id) as rows FROM users WHERE valid = 1" . $and)->fetch(PDO::FETCH_OBJ);
return $total->rows;
}
From my point of view this is totally wrong, the statement is not prepared and is passed directly from user input without any validation that can lead to SQL Injection, so I proposed this to the trainer (I know fetchColumn() would be more appropriate here but let's stick with this for the sake of the example):
function countUsers($search) {
$and = '';
$sqlSearch = "%$search%";
if ($search != '') {
$and = " AND user_name LIKE :username";
}
$sql = "SELECT COUNT(id) as rows FROM users WHERE valid = 1" . $and;
$sth = $this->db->prepare($sql);
if ($search != '') {
$sth->bindParam(':username', $sqlSearch, PDO::PARAM_STR);
}
$sth->execute();
$total = $sth->fetch(PDO::FETCH_OBJ);
return $total->rows;
}
Am I wrong? Are they wrong or we both wrong/right?

Yes you are right.
However, your code is not optimal. In fact, prepared statements are intended to make your code cleaner, not more bloated.
function countUsers($search) {
$sql = "SELECT COUNT(id) FROM users WHERE valid = 1 AND user_name LIKE ?";
$sth = $this->db->prepare($sql);
$sth->execute(["%$search%"]);
return $sth->fetchColumn();
}
Part of the cleanup I did is a mere trick - as you can always search for LIKE '%%' and match all rows (excluding ones where user_name is null though).
But the rest is just a proper use of PDO features:
you can always use positional placeholders
you can always avoid bindParam() call
you should use appropriate fetch mode

Related

Full text search with mysql php

I am trying to make a search key feature. But I am not getting any result with the following query.
public function SearchKey($key,$userid)
{
$key = mysqli_real_escape_string($this->db, $key);
$userid = mysqli_real_escape_string($this->db, $userid);
$query = mysqli_query($this->db,"SELECT * FROM posts WHERE
MATCH(theKey) AGAINST('$key' IN NATURAL LANGUAGE MODE)
AND uid = '$userid' ORDER BY sgq_id LIMIT 5") or die(mysqli_error($this->db));
while($row=mysqli_fetch_array($query)) {
$data[]=$row;
}
if(!empty($data)) {
return $data;
}
}
Then fetch,
$search = $Data->SearchKey($key, $userid);
if($search){
foreach($search as $data){
echo $data['theKey'];
}
}
For example if I search OK005 then I can not get any results. I tried Full-text Search functions https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html
Anyone can help me here, what I am missing ?
You're using single quotes to pass your variables. These will not be expanded in your query. You're better off using a prepared statement, and use parameter/value bindings to pass the variables. This will also solve the problem of SQL injection that your code appears to be vulnerable to.
You can try something like:
// Replace comment with appropriate connection data.
$pdo = new PDO(/* your DSN etc. */);
// Your query.
$sql =
'SELECT * FROM posts WHERE ' .
'MATCH(theKey) AGAINST(? IN NATURAL LANGUAGE MODE) ' .
'AND uid = ? ORDER BY sgq_id LIMIT 5';
// Create prepared statement from query.
$statement = $pdo->prepare($sql);
// Bind the values and enforce data type.
$statement->bindValue(1, $key, PDO::PARAM_STR);
$statement->bindValue(2, $userid, PDO::PARAM_INT);
// Run query.
$statement->execute();
// Get query results.
$rows = $statement->fetchAll();
// Your magic ...

How to write PHP code (using prepared statements) to return rows from MySQL DB when some input parameters use a value 'all' as wildcard?

I'm a PHP novice and am working to create a webform using PHP and MySQL (I have some experience with MySQL but at most am at the intermediate level). The front-end webform lets the user enter values of the following 4 parameters: genre, composer, instrument, and ensemble. Each of these parameters can either take a proper name (e.g., "classical" for genre, "violin" for instrument, etc.), or a value called "all". If "all", then the MySQL database will return all the rows of that parameter or parameters (filtered, of course, on the other parameters that are not "all"). I also want to write the PHP codes using prepared statements.
So, I wrote the following PHP code:
// The variables take the values entered by user in the webform
$genre = htmlspecialchars($_POST['genre']);
$composer = htmlspecialchars($_POST['composer']);
$instrument = htmlspecialchars($_POST['instrument']);
$ensemble = htmlspecialchars($_POST['ensemble']);
// Prepare the MySQL query and execute it using prepared statements
$sql_query = "SELECT db_genre, db_composer, db_instrument, db_ensemble FROM recording_metadata
WHERE (CASE WHEN ?='all' THEN true ELSE column=genre END) AND
(CASE WHEN ?='all' THEN true ELSE column=composer END) AND
(CASE WHEN ?='all' THEN true ELSE column=instrument END) AND
(CASE WHEN ?='all' THEN true ELSE column=ensemble END)";
$stmt = mysqli_stmt_init($con); //$con defined earlier by mysqli_connect
if (mysqli_stmt_prepare($stmt, $sql_query)) {
mysqli_stmt_bind_param($stmt, "ssss", $genre, $composer, $instrument, $ensemble);
}
mysqli_stmt_execute($stmt);
mysqli_stmt_store_result($stmt);
mysqli_stmt_bind_result($stmt, $genre, $composer, $instrument, $ensemble);
I only know that the WHERE clause is incorrectly written because some error in it invalidates the execution of the subsequent mysqli_stmt_* commands. I don't know what that error is or how to fix it.
I appreciate if anyone can tell me how to write the WHERE clause that will return the desired rows when one or more parameter value is "all". Thanks in advance.
I would rather to vary the statement using PHP instead of MySQL command:
// Prepare the WHERE clause using PHP
$genre = htmlspecialchars($_POST['genre']);
$composer = htmlspecialchars($_POST['composer']);
$instrument = htmlspecialchars($_POST['instrument']);
$ensemble = htmlspecialchars($_POST['ensemble']);
$where = ' 1 = 1'; //First condition which is always true
if ($genre != 'All') {
$where .= " AND db_genre = $genre"; //Append to the $where variables if genre is not 'All'
}
if ($composer != 'All') {
$where .= " AND db_composer = $composer";
}
if ($instrument != 'All') {
$where .= " AND db_instrument = $instrument";
}
if ($ensemble != 'All') {
$where .= " AND db_ensemble = $ensemble";
}
// Prepare the SQL query based on the variable above
$sql_query = "SELECT db_genre, db_composer, db_instrument, db_ensemble FROM recording_metadata $where";
Then, the column query will only included if the input is not 'All'.

How to ignore a parameter in a prepared mysqli query in PHP?

I have a prepared mysqli query like this:
$query = $database->prepare("SELECT * FROM items WHERE inStock > ? AND size < ? AND name LIKE ?");
$query->bind_param('iis', $inStock, $size, $name);
$query->execute();
There are many various conditions in the WHERE clause which filter out the results.
The problem is, that those parameters are supplied in a search form and they aren't mandatory. For example, someone can search by using only the name, or only by using the size and name, or by using the size, name and inStock, all at the same time.
I need some way to adjust the query so I can supply only the parameters I want. The only solution I can think of, is to make a huge if..else structure where prepared queries with all combinations of the search options exist, but that is out of the question as there are thousands of combinations.
The only actual realistic solution I can think of, would be to use a not prepared query, where I glue the conditions together with from pieces like $query .= "AND name LIKE '%".escapestuff($_POST['name'])."%'"
But that is very ugly and I would very much like to stay with the prepared query system.
You can build up a list of the criteria and add into a list the bind values and types, here is a quick mock up which uses two of the fields you refer to...
$data = [];
$params = "";
$where = [];
if ( !empty($name)) {
$data[] = $name;
$params.="s";
$where[] = "name like ?";
}
if ( !empty($size)) {
$data[] = $size;
$params.="i";
$where[] = "size < ?";
}
$sql = "SELECT * FROM items";
if ( count($where) > 0 ){
$sql .= " where ". implode ( " and ", $where);
}
$query = $database->prepare($sql);
$query->bind_param($params, ...$data);
$query->execute();
Notice that the bind_param() uses the ... to allow you to pass an array instead of the individual fields.

Conditional query with PDO prepare and bind statement

I am converting all my queries from mysql to PDO, and in this process I found a conditional query like a follows
if (isset($parameters['searchTerm'])) {
$where =" And title LIKE '%{$parameters['searchTerm'] }%'";
}
$sql = "Select * from table data Where tableId = 5 {$where} ";
and when I am trying to convert this query in PDO the expected syntax is as follows
if (isset($parameters['searchTerm'])) {
$where =" And title LIKE :searchTerm";
}
$sql = $dbh->prepare("Select * from table data Where tableId = 5 {$where}");
if (isset($parameters['searchTerm'])) {
$sql ->bindParam(':searchTerm', '%{$parameters['searchTerm'] }%');
}
$sql ->execute();
Now as you can See that the if condition if (isset ($parameters ['searchTerm'] )) {...} is repeated twice.
The reason is
I can not prepare the sql query before $where is being set thus $sql variable is initialized after first if statement
I can not bind the parameters until I prepare the sql so it has to be placed after the $sql is being prepared
So there is one if statement before $sql = $dbh->prepare("Select * from table data Where tableId = 5 {$where}"); and one if statement after.
And my question is: Is there a way to remove this redundant if statement or I have to do it this way only.
you can use handy PDO's feature that lets you to send array with parameters straight into execute()
$where = '';
$params = array();
if (isset($parameters['searchTerm'])) {
$where =" And title LIKE :searchTerm";
$params['searchTerm'] = "%$parameters[searchTerm]%";
}
$sql = "Select * from table data Where tableId = 5 $where";
$pdo->prepare($sql)->execute($params);
Note that PHP syntax in your code is also wrong.

Possible to have PHP MYSQL query ignore empty variable in WHERE clause?

Not sure how I can do this. Basically I have variables that are populated with a combobox and then passed on to form the filters for a MQSQL query via the where clause. What I need to do is allow the combo box to be left empty by the user and then have that variable ignored in the where clause. Is this possible?
i.e., from this code. Assume that the combobox that populates $value1 is left empty, is there any way to have this ignored and only the 2nd filter applied.
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Thanks for any help.
C
Use
$where = "WHERE user_id = '$username'";
if(!empty($value1)){
$where .= "and location = '$value1'";
}
if(!empty($value2 )){
$where .= "and english_name= '$value2 '";
}
$query = "SELECT * FROM moth_sightings $where";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Several other answers mention the risk of SQL injection, and a couple explicitly mention using prepared statements, but none of them explicitly show how you might do that, which might be a big ask for a beginner.
My current preferred method of solving this problem uses a MySQL "IF" statement to check whether the parameter in question is null/empty/0 (depending on type). If it is empty, then it compares the field value against itself ( WHERE field1=field1 always returns true). If the parameter is not empty/null/zero, the field value is compared against the parameter.
So here's an example using MySQLi prepared statements (assuming $mysqli is an already-instantiated mysqli object):
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = ?
AND location = IF(? = '', location, ?)
AND english_name = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('ssss', $username, $value1, $value1, $value2);
$stmt->execute();
(I'm assuming that $value2 is a string based on the field name, despite the lack of quotes in OP's example SQL.)
There is no way in MySQLi to bind the same parameter to multiple placeholders within the statement, so we have to explicitly bind $value1 twice. The advantage that MySQLi has in this case is the explicit typing of the parameter - if we pass in $value1 as a string, we know that we need to compare it against the empty string ''. If $value1 were an integer value, we could explicitly declare that like so:
$stmt->bind_param('siis', $username, $value1, $value1, $value2);
and compare it against 0 instead.
Here is a PDO example using named parameters, because I think they result in much more readable code with less counting:
$sql = "SELECT *
FROM moth_sightings
WHERE user_id = :user_id
AND location = IF(:location_id = '', location, :location_id)
AND english_name = :name";
$stmt = $pdo->prepare($sql);
$params = [
':user_id' => $username,
':location_id' => $value1,
':name' => $value2
];
$stmt->execute($params);
Note that with PDO named parameters, we can refer to :location_id multiple times in the query while only having to bind it once.
if ( isset($value1) )
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND location = '$value1' AND english_name = $value2 ";
else
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' AND english_name = $value2 ";
But, you can also make a function to return the query based on the inputs you have.
And also don't forget to escape your $values before generating the query.
1.) don't use the simply mysql php extension, use either the advanced mysqli extension or the much safer PDO / MDB2 wrappers.
2.) don't specify the full statement like that (apart from that you dont even encode and escape the values given...). Instead use something like this:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s", ...);
Then fill that raw query using an array holding all values you actually get from your form:
$clause=array(
'user_id="'.$username.'"',
'location="'.$value1.'"',
'english_name="'.$value2.'"'
);
You can manipulate this array in any way, for example testing for empty values or whatever. Now just implode the array to complete the raw question from above:
sprintf("SELECT * FROM moth_sightings WHERE 1=1 AND %s",
implode(' AND ', $clause) );
Big advantage: even if the clause array is completely empty the query syntax is valid.
First, please read about SQL Injections.
Second, $r = mysql_numrows($result) should be $r = mysql_num_rows($result);
You can use IF in MySQL, something like this:
SELECT * FROM moth_sightings WHERE user_id = '$username' AND IF('$value1'!='',location = '$value1',1) AND IF('$value2'!='',english_name = '$value2',1); -- BUT PLEASE READ ABOUT SQL Injections. Your code is not safe.
Sure,
$sql = "";
if(!empty($value1))
$sql = "AND location = '{$value1}' ";
if(!empty($value2))
$sql .= "AND english_name = '{$value2}'";
$query = "SELECT * FROM moth_sightings WHERE user_id = '$username' {$sql} ";
$result = mysql_query($query) or die(mysql_error());
$r = mysql_numrows($result);
Be aware of sql injection and deprecation of mysql_*, use mysqli or PDO instead
I thought of two other ways to solve this:
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND location = '%$value1%'
AND english_name = $value2 ";
This will return results only for this user_id, where the location field contains $value1. If $value1 is empty, this will still return all rows for this user_id, blank or not.
OR
SELECT * FROM moth_sightings
WHERE
user_id = '$username'
AND (location = '$value1' OR location IS NULL OR location = '')
AND english_name = $value2 ";
This will give you all rows for this user_id that have $value1 for location or have blank values.

Categories