Full text search with mysql php - 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 ...

Related

PDO prepared statement bind parameters once for different queries

I am using PDO prepared statements to execute two queries:
SELECT count(*) FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson;
SELECT * FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson limit 100;
The first query to get the count works as expected and i get the row count.
$stmt = $this->connection->prepare($sql);
foreach ($params as $key => $value)
$stmt->bindValue(":" . $key, $value, PDO::PARAM_STR);
$stmt->execute();
$count = $stmt->fetchColumn();
$sql .= " limit $limit;";
$sql = str_replace("count(*)", $columns, $sql);
$stmt = $this->connection->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_CLASS, $class);
But when executing the second query i get:
SQLSTATE[HY093]: Invalid parameter number: no parameters were bound
Therefore, I would like to know, if I have multiple queries where the parameters are exactly the same ,if I need to bind the same parameters again using
foreach ($params as $key => $value)
$stmt->bindValue(":" . $key, $value, PDO::PARAM_STR);
or if there is a way to bind parameters only once.
If I have multiple queries where the parameters are exactly the same, do I need to bind the same parameters again using
Yes, of course.
Parameters are bound to each query, not to PDO or a database globally.
On a side note, with PDO you don't have to bind variables explicitly, so there is a solution to your "problem": just don't bind at all but send your data directly into execute() as it shown in the Dharman's excellent answer
There is no need to modify your SQL like this. Your code basically comes down to this:
$stmt = $this->connection->prepare('SELECT count(*) FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson');
$stmt->execute($params);
$count = $stmt->fetchColumn();
$stmt = $this->connection->prepare('SELECT * FROM vocabulary WHERE `type` = :type AND `lesson` = :lesson limit 100');
$stmt->execute($params);
$result = $stmt->fetchAll(PDO::FETCH_CLASS, $class);

PHP PDO MySQL count() prepared statement

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

Best way to avoid preparing the same PDO statement more than once?

Currently I save prepared statements into a private variable, because I ignore how they really work in the deepness, and do it just in case.
So the question is really simple, if I iterate over the same $PDO->prepare(), will it prepare again the same query?
foreach( $arr as $docid ) {
if( $this->dbLink === null ) { // PDO resource, saved in the object.
throw new Exception( 'Must first connect to DB' );
}
if( $this->queryCheckAccess === null ) {
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
}
else {
$result = $this->queryCheckAccess->execute(array(':id'=>$docid));
}
}
Will it matter ? Or the DB Engine / PHP is smart enough to know that it is the same prepared statement?
Thanks a lot.
----------------- EDIT --------------
I think I was misunderstood.
What I ask is what happens if I do:
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
And what happens if I do:
if( $this->queryCheckAccess === null ) {
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
}
Will the engine prepare the query 4 times in the first example? Or will notice it is the same query and just "jump" that?
Your code only prepares the query once, because after the first loop iteration, it's not NULL so it the conditional block won't run. But it's a waste of time to check the condition every time through the loop.
But to answer your question, if you prepare() the same query, it does do redundant work, even if the query is identical to the one you prepared before. So you should avoid that.
But you don't need to prepare inside the loop at all. Prepare once before you start the loop, and bind a variable to the parameter. You don't need to bind every time in the loop, just change the value of that variable.
if( $this->dbLink === null ) { // PDO resource, saved in the object.
throw new Exception( 'Must first connect to DB' );
}
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$this->queryCheckAccess->bindParam(':id' => $docidparam);
foreach( $arr as $docid ) {
$docidparam = $docid;
$result = $this->queryCheckAccess->execute();
}
I'm not sure if you can bind the variable and also use it as the loop variable, there might be a scope conflict.
Another suggestion for this query: why not just run one query to search for a list of values?
$list = implode(",", array_fill(1, count($arr), "?"));
$query = "SELECT * FROM something WHERE id IN ( $list )";
$this->queryCheckAccess = $this->dbLink->prepare($query);
$this->queryCheckAccess->execute($arr);
PS: Also you should check for errors. If you enable PDO error mode EXCEPTION, then errors will automatically throw exceptions. If you don't enable that mode, you need to check the return value of prepare() and execute(), which return false if there's an error.
I just RUN a code similar to your example, and enabled MySQL Query LOG I found that all prepare requests are sent to MySQL Server
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Test code:
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth->bindParam(1, $user);
$sth->execute();
Then, the best way is to prepare once, and Bind different values and then execute.
$sth = $dbh->prepare($sql);
$user = "test";
$sth->bindParam(1, $user);
$sth->execute();
$user = "test2";
$sth->bindParam(1, $user);
$sth->execute();
$user = "test";
$sth->bindParam(1, $user);
$sth->execute();
No, that's one of the main features of prepared statements. If you're going to run the same query multiple times but with different variables then preparing the query will give you a speed increase. Especially if you make use of transactions (requires InnoDB storage engine).
To answer the question from the title (which is quite different from questions in the body), the best way to avoid preparing the same statement more than once, apparently would be to avoid running multiple similar queries at all.
To answer the question from the question body - no, DB Engine / PHP is not "smart" enough to know that it is the same query were prepared again. With every new prepare() call another statement is created. And I would be first to hate such a "smart" behavior. The "smarter" your tool, the more unpredictable results you get.
To answer the real problem in the code, a smart developer would use a right tool to save himself a trouble.
With safeMysql whole mess will be reduced to one query and one line of code
$data = $this->dbLink->getAll('SELECT * from somth where id IN (?a)', $arr);
S0 - no multiple queries, no multiple preparations, no multiple questions.
By the way, you are losing first id with your code.
Yet you're losing all of them but last one if you don't use the result in place.

WHERE statement inside if condition in SQL

Can I do a WHERE clause inside an IF statement?
Like I want something like this:
$SQL = mysql_query("SELECT * FROM `table` ORDER BY `row` DESC");
$rows = mysql_fetch_array($SQL);
$email = $_SESSION['email_of_user'];
if($rows["row"] == "1" WHERE `row`='$email' : ?> (Pulls the logged in user's email)
Edit Server
<?php else : ?>
Add Server
<?php endif; ?>
Do I need (" where the WHERE statement is? Because I tried that and it didn't seem to work...
Or can I do it with an if condition inside of a where clause? Not sure of all these terms yet so correct me if I'm wrong...
You cannot mix up a query statement with PHP's statement. Instead write a query extracting desired results and check if there are any rows from that query.
I will show you an example:
$query = "SELECT * FROM `TABLE_NAME` WHERE `field` = '1' && `email`='$email'"; //Create similar query
$result = mysqli_query($query, $link); //Query the server
if(mysqli_num_rows($result)) { //Check if there are rows
$authenticated = true; //if there is, set a boolean variable to denote the authentication
}
//Then do what you want
if($authenticated) {
echo "Edit Server";
} else {
echo "Add Server";
}
Since Aaron has shown such a effort to encourage safe code in my example. Here is how you can do this securely. PDO Library provides options to bind params to the query statement in the safe way. So, here is how to do it.
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass); //Create the connection
//Create the Query Statemetn
$sth = $dbh->prepare('SELECT * FROM `TABLE_NAME` WHERE field = :field AND email = :email');
//Binds Parameters in the safe way
$sth -> bindParam(':field', 1, PDO::PARAM_INT);
$sth -> bindParam(':email', $email, PDO::PARAM_STRING);
//Then Execute the statement
$sth->execute();
$result = $sth->fetchAll(); //This returns the result set as an associative array

How to create a secure mysql prepared statement in php?

I am new to using prepared statements in mysql with php. I need some help creating a prepared statement to retrieve columns.
I need to get information from different columns. Currently for a test file, I use the completely unsecure SQL statement:
$qry = "SELECT * FROM mytable where userid='{$_GET['userid']}' AND category='{$_GET['category']}'ORDER BY id DESC"
$result = mysql_query($qry) or die(mysql_error());
Can someone help me create a secure mysql statement using input from url parameters (as above) that is prepared?
BONUS: Prepared statements are suppose to increase speed as well. Will it increase overall speed if I only use a prepared statement three or four times on a page?
Here's an example using mysqli (object-syntax - fairly easy to translate to function syntax if you desire):
$db = new mysqli("host","user","pw","database");
$stmt = $db->prepare("SELECT * FROM mytable where userid=? AND category=? ORDER BY id DESC");
$stmt->bind_param('ii', intval($_GET['userid']), intval($_GET['category']));
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($column1, $column2, $column3);
while($stmt->fetch())
{
echo "col1=$column1, col2=$column2, col3=$column3 \n";
}
$stmt->close();
Also, if you want an easy way to grab associative arrays (for use with SELECT *) instead of having to specify exactly what variables to bind to, here's a handy function:
function stmt_bind_assoc (&$stmt, &$out) {
$data = mysqli_stmt_result_metadata($stmt);
$fields = array();
$out = array();
$fields[0] = $stmt;
$count = 1;
while($field = mysqli_fetch_field($data)) {
$fields[$count] = &$out[$field->name];
$count++;
}
call_user_func_array(mysqli_stmt_bind_result, $fields);
}
To use it, just invoke it instead of calling bind_result:
$stmt->store_result();
$resultrow = array();
stmt_bind_assoc($stmt, $resultrow);
while($stmt->fetch())
{
print_r($resultrow);
}
You can write this instead:
$qry = "SELECT * FROM mytable where userid='";
$qry.= mysql_real_escape_string($_GET['userid'])."' AND category='";
$qry.= mysql_real_escape_string($_GET['category'])."' ORDER BY id DESC";
But to use prepared statements you better use a generic library, like PDO
<?php
/* Execute a prepared statement by passing an array of values */
$sth = $dbh->prepare('SELECT * FROM mytable where userid=? and category=?
order by id DESC');
$sth->execute(array($_GET['userid'],$_GET['category']));
//Consider a while and $sth->fetch() to fetch rows one by one
$allRows = $sth->fetchAll();
?>
Or, using mysqli
<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$category = $_GET['category'];
$userid = $_GET['userid'];
/* create a prepared statement */
if ($stmt = mysqli_prepare($link, 'SELECT col1, col2 FROM mytable where
userid=? and category=? order by id DESC')) {
/* bind parameters for markers */
/* Assumes userid is integer and category is string */
mysqli_stmt_bind_param($stmt, "is", $userid, $category);
/* execute query */
mysqli_stmt_execute($stmt);
/* bind result variables */
mysqli_stmt_bind_result($stmt, $col1, $col2);
/* fetch value */
mysqli_stmt_fetch($stmt);
/* Alternative, use a while:
while (mysqli_stmt_fetch($stmt)) {
// use $col1 and $col2
}
*/
/* use $col1 and $col2 */
echo "COL1: $col1 COL2: $col2\n";
/* close statement */
mysqli_stmt_close($stmt);
}
/* close connection */
mysqli_close($link);
?>
I agree with several other answers:
PHP's ext/mysql has no support for parameterized SQL statements.
Query parameters are considered more reliable in protecting against SQL injection issues.
mysql_real_escape_string() can also be effective if you use it correctly, but it's more verbose to code.
In some versions, international character sets have cases of characters that are not escaped properly, leaving subtle vulnerabilities. Using query parameters avoids these cases.
You should also note that you still have to be cautious about SQL injection even if you use query parameters, because parameters only take the place of literal values in SQL queries. If you build SQL queries dynamically and use PHP variables for the table name, column name, or any other part of SQL syntax, neither query parameters nor mysql_real_escape_string() help in this case. For example:
$query = "SELECT * FROM $the_table ORDER BY $some_column";
Regarding performance:
The performance benefit comes when you execute a prepared query multiple times with different parameter values. You avoid the overhead of parsing and preparing the query. But how often do you need to execute the same SQL query many times in the same PHP request?
Even when you can take advantage of this performance benefit, it is usually only a slight improvement compared to many other things you could do to address performance, like using opcode caching or data caching effectively.
There are even some cases where a prepared query harms performance. For example in the following case, the optimizer can't assume it can use an index for the search, because it must assume the parameter value might begin with a wildcard:
SELECT * FROM mytable WHERE textfield LIKE ?
Security with MySQL in PHP (or any other language for that matter) is a largely discussed issue. Here are a few places for you to pick up some great tips:
http://webmaster-forums.code-head.com/showthread.php?t=939
http://www.sitepoint.com/article/php-security-blunders/
http://dev.mysql.com/tech-resources/articles/guide-to-php-security.html
http://www.scribd.com/doc/17638718/Module-11-PHP-MySQL-Database-Security-16
The two most major items in my opinion are:
SQL Injection: Be sure to escape all of your query variables with PHP's mysql_real_escape_string() function (or something similar).
Input Validation: Never trust the user's input. See this for a tutorial on how to properly sanitize and validation your inputs.
If you're going to use mysqli - which seems the best solution to me - I highly recommend downloading a copy of the codesense_mysqli class.
It's a neat little class that wraps up and hides most of the cruft that accumulates when using raw mysqli such that using prepared statements only takes a line or two extra over the old mysql/php interface
Quite late, but this might help someone:
/**
* Execute query method. Executes a user defined query
*
* #param string $query the query statement
* #param array(Indexed) $col_vars the column variables to replace parameters. The count value should equal the number of supplied parameters
*
* Note: Use parameters in the query then supply the respective replacement variables in the second method parameter. e.g. 'SELECT COUNT(*) as count FROM foo WHERE bar = ?'
*
* #return array
*/
function read_sql($query, $col_vars=null) {
$conn = new mysqli('hostname', 'username', 'user_pass', 'dbname');
$types = $variables = array();
if (isset($col_vars)) {
for ($x=0; $x<count($col_vars); $x++) {
switch (gettype($col_vars[$x])) {
case 'integer':
array_push($types, 'i');
break;
case 'double':
array_push($types, 'd');
break;
default:
array_push($types, 's');
}
array_push($variables, $col_vars[$x]);
}
$types = implode('', $types);
$sql = $conn->prepare($query);
$sql -> bind_param($types, ...$variables);
$sql -> execute();
$results = $sql -> get_result();
$sql -> close();
}else {
$results = $conn->query($query) or die('Error: '.$conn->error);
}
if ($results -> num_rows > 0) {
while ($row = $results->fetch_assoc()) {
$result[] = $row;
}
return $result;
}else {
return null;
}
}
You can then invoke the function like so:
read_sql('SELECT * FROM mytable where userid = ? AND category = ? ORDER BY id DESC', array($_GET['userid'], $_GET['category']));

Categories