0

I'm looking for a way to get lots of user inputs, concatenate them into one sql query, and return the results from my database. I have tried a few different techniques so far including putting all the variables into an array then using implode() but I couldn't get it to work. For simplicity sake I have decided to just go with a couple of if statements to check if each variable has a value in it or not. If it does then it should add some sql. My error message from this is as follows:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AND type = AND (city LIKE '%%') OR (addressLineOne LIKE '%%') OR (`addres' at line 1

It appears that $type is not being picked up even though I gave it a input during the test. I have not given any other inputs values besides $type and $bedroom. Any help and improvement on the code would be greatly appreciated. I'm new to PHP and SQL so sorry if it's something stupid, but I have tried to fix this for ages.

HTML

<form action="searchresults.php" method="get">
        <fieldset>
          <legend><h3>Search</h3></legend>
            <p>Please enter criteria for your search.</p>
            <label for="location">Location</label>
            <input type="text" name="location" />
            <select name="type">
              <option value="Studio Flat" selected>Studio Flat</option>
              <option value="Flat">Flat</option>
              <option value="Detached">Detached</option>
              <option value="Semi-detached">Semi-detached</option>
              <option value="Terraced">Terraced</option>
              <option value="Bungalow">Bungalow</option>
            </select>
            <label for="bedroom">Bedrooms</label>
            <select name="bedroom">
              <option value="1" selected>1</option>
              <option value="2">2</option>
              <option value="3">3</option>
              <option value="4">4</option>
              <option value="5">5</option>
              <option value="6">6</option>
              <option value="7">7</option>
              <option value="8">8</option>
              <option value="9">9</option>
              <option value="10">10</option>
            </select>
            <label for="min">Min Price</label>
            <input type="number" name="min" />
            <label for="max">Max Price</label>
            <input type="number" name="max" />
                <br />
            <input type="submit" value="Search" />
        </fieldset>
      </form>

PHP

<?php
session_start();
include './auth.php'; // connection to db

$location = trim($_POST['location']);
$location = strip_tags($location);
$location = htmlspecialchars($location);

$bedroom = trim($_POST['bedroom']);
$bedroom = strip_tags($bedroom);
$bedroom = htmlspecialchars($bedroom);

$type = trim($_POST['type']);
$type = strip_tags($type);
$type = htmlspecialchars($type);

$max = trim($_POST['max']);
$max = strip_tags($max);
$max = htmlspecialchars($max);

$min = trim($_POST['min']);
$min = strip_tags($min);
$min = htmlspecialchars($min);


// build query
$query = "SELECT * FROM Listings WHERE `bedroom` = ".$bedroom." AND `type` = ".$type."";

if(isset($location)){
  $query .= " AND (`city` LIKE '%".$location."%') OR (`addressLineOne` LIKE '%".$location."%') OR (`addressLineTwo` LIKE '%".$location."%') OR (`county` LIKE '%".$location."%')";
}
if(isset($max)){
  $query .= " AND (`price` <= '%".$price."%')";
}
if(isset($min)){
  $query .= " AND (`price` >= '%".$price."%')";
}

$query .= "ORDER BY price;";

// send query to database and return error if it fails
$input = mysqli_query($connect, $query) or die(mysqli_error($connect));
  // output results
  if(mysqli_num_rows($input)>0){ // if one or more results returned do this code
    while($result = mysqli_fetch_array($input)){ // puts data in array then loops the following code
      echo "<p><h3>".$result['addressLineOne']." ".$result['addressLineTwo']."
      ".$result['location']."</h3><h4>£".$result['price']."</h4>".$result['information']."</p><br /><hr />";
    }
    }else{ // no results then print the following
      echo "Sorry, we couldn't find any results.
        Please refine your search and try again.";
  }

  echo $query;
  // close the connection
  mysqli_close($connect)
?>
4
  • This is not how to build a query. See here: stackoverflow.com/documentation/php/2784/php-mysqli/11958/… Commented Feb 15, 2017 at 22:29
  • You are wide open to SQL Injections and should really use Prepared Statements instead of concatenating your queries. htmlspecialchars is not enough to protect you. Commented Feb 15, 2017 at 22:32
  • 1
    WHERE bedroom = ".$bedroom." obviously failed you for starters. Commented Feb 15, 2017 at 22:38
  • @MagnusEriksson okay, thanks for the heads up. Commented Feb 15, 2017 at 22:49

2 Answers 2

1

I know you're currently using mysqli, but PDO makes building dynamic queries much easier, so I strongly suggest you switch to it, if you're not very far along on this project.

In a mysqli prepared statement, you have to call mysqli_stmt::bind_param(), passing every parameter in the argument list. In contrast, PDO requires no binding, and the parameters are all passed to PDOStatement::execute() in an array. This answer will show you how your code would work with PDO.

<?php

$connection = new \PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);

$query = "SELECT * FROM Listings WHERE bedroom = :bed AND type = :type AND (city LIKE :loc OR addressLineOne LIKE :loc OR addressLineTwo LIKE :loc OR county LIKE :loc)";
$parameters = [
    ":bed" => $_POST["bedroom"],
    ":type" => $_POST["type"],
    ":loc" => "%$_POST[location]%",
];

if(!empty($_POST["max"])) {
    $query .= " AND price <= :max";
    $parameters[":max"] = $_POST["max"];
}

if (!empty($_POST["min"])) {
    $query .= " AND price >= :min";
    $parameters[":min"] = $_POST["min"];
}

$query .= " ORDER BY price";

$stmt = $connection->prepare($query);
$stmt->execute($parameters);
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);

if (empty($results)) { // no results then print the following
    echo "Sorry, we couldn't find any results. Please refine your search and try again.";
}
foreach ($results as $result) {
    //escape for HTML output
    $result = array_map("htmlspecialchars", $result);
    echo <<< HTML
<p>
<h3>$result[addressLineOne] $result[addressLineTwo] $result[location]</h3>
<h4>£$result[price]</h4>
$result[information]
</p>
<br />
<hr />

HTML;
}

I've also simplified your HTML output by using a heredoc string, but you should really have your HTML and PHP separated.

If this is part of a much bigger existing project, you will likely be sticking with mysqli, in which case I urge you to learn how to use prepared statements; the days of building queries with string concatenation are long behind us!

Sign up to request clarification or add additional context in comments.

Comments

0

Using PDO and bound parameters as @miken32 suggests is a possibility, but I advise against it because it has several downsides:

  • You are currently using mysqli, which also supports bound parameters, so no reason to switch to PDO just to get parameter binding
  • @miken32's solution uses a named parameter (something only PDO supports) multiple times in the query. This only works when client-side parameter binding is enabled (i.e. PDO::ATTR_EMULATE_PREPARES set to true) which itself has multiple problems:
    • I could not quickly find out what the default for PDO::ATTR_EMULATE_PREPARES is, apparently "it depends on the driver".
    • With PDO::ATTR_EMULATE_PREPARES switched on, you cannot use bound parameters in your LIMIT clause anymore
    • The errors returned from PDO are completely different depending on whether you use client-side or server-side parameter binding, a missing parameter for example causes an SQLSTATE of HY000 with server-side and HY093 with client-side parameter binding. This complicates proper error handling.
  • There is no way to see the actual query, an ability that can be very useful (essential in my opinion) for debugging.

So, I say go ahead and build your query manually, but in a clean and safe way. To do that you need to understand these concepts:

HTML encoding

In general, the only place where you need htmlspecialchars() is when you pass data (for example from the database) to the browser.

So, change every

$location = trim($_POST['location']);
$location = strip_tags($location);
$location = htmlspecialchars($location);

to

$location = trim($_POST['location']);

There is no need for strip_tags() nor htmlspecialchars() at this point. Form data from the browser arrives on the PHP side without any encoding. Of course, if someone would actually enter some<br>city into the location field, he would not find any rooms, but it will not break anything if you get the rest of your application right.

Also change every

echo "<p>".$result['addressLineOne']." ... </p><br /><hr />";

to

echo "<p>".htmlspecialchars($result['addressLineOne'])." ... </p><br /><hr />";

Otherwise the people entering the data into the database could run a "cross site scripting attack" - maliciously or accidentally.

SQL encoding

When sending data to the database as part of a query, you have to encode it in a way that the database knows that it's variable data and not part of the SQL command.

So, instead of for example

$query .= " AND (`city` LIKE '%".$location."%') ";

you have to write

$query .= " AND (`city` LIKE '%".addslashes($location)."%') ";

this makes sure that if there is a quote character (') inside your $location variable it will be escaped, so it does not end the string at that point.

If you work with a character set other than UTF-8, you should use mysql_real_escape_string() instead of addslashes() but you are probably using UTF-8, so it's just fine.

Furthermore in this case you might want to remove or escape % and _ in the value, because they have a special meaning in a LIKE query.

You have two more problems in your code:

$query = "SELECT * FROM Listings WHERE `bedroom` = ".$bedroom." AND `type` = ".$type."";

Here you not only have to add addslashes() but also the quotes around the value:

$query = "SELECT * FROM Listings WHERE `bedroom` = '".addslashes($bedroom)."' AND `type` = '".addslashes($type)."'";

And here:

$query .= " AND (`price` <= '%".$price."%')";

Obviously the percent signs make no sense here, you clearly meant:

$query .= " AND (`price` <= '".addslashes($price)."')";

Libraries and template engines

Later on you would be well advised using a library or framework that does those things for you (because it's very easy to forget an addslashes() somewhere), but it can't hurt to do it manually at first in order to learn how it works under the hood.

4 Comments

Your ideas are peculiar, to say the least
@YourCommonSense There is certainly nothing elegant about my answer but given the level of the question it's hard to come up with anything elegant. However, if you find anything that is plainly wrong, maybe you could elaborate in an answer of your own? I'm always interested in upgrading my ideas.
Well, there is nothing essentially wrong with your statements, I have to admit. Every single one is flawless. You demonstrate the knowledge far beyond the level of the average participant here. But still I can't help disliking the message at whole. It's very hard a task to move the collective consciousness toward prepared statements. And your suggestion, though correct by itself, is a step back on this road.
I see and support your point but with the current state of the implementation and documentation of prepared statements in both PDO and mysqli it's no wonder that it's hard to get people to use it. Recommending a library like MeekroDB also felt like a bad fit here, so I decided to teach the basics to enable the OP to evaluate and verify his choices on his own.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.