I currently have a small search form with only two elements: a text input named search
and a "select" box named param
. The option selected within that box should be used as a parameter to choose which table column in my DB would be fetched.
Using mysql_
functions, something like this could be done:
$param = $_POST['param' ];
$search = $_POST['search'];
$query = 'SELECT * FROM table WHERE $param LIKE "%$search%"';
But I can't get it to work with the mysqli_
syntax. I am trying to use prepared statements, but the best I've done so far is this:
$param = $_POST['param' ];
$search = $_POST['search'];
if($param == 'first_name')
{
if($prep = $link->prepare('SELECT * FROM table
WHERE first_name
LIKE CONCAT("%", ?, "%")'))
{
$prep->bind_param('s', $search);
$prep->execute();
$prep->bind_result($first_name, $last_name);
while($prep->fetch())
echo $first_name . ' ' . $last_name;
$prep->close();
}
else
echo 'Error while preparing statement.';
}
else if($param == 'last_name')
{
...
}
But just using a bunch of else if
s seems a lot repetitive and unproductive, specially if I have lots of columns to handle.
The first thing I tried was parameter binding - ... WHERE ? LIKE ...
and $prep->bind_param('ss', $param, $search)
-, but it didn't work (and I still don't know why).
Is there a way of doing it in a more intelligent way?
If you are using the same SQL code for every param, just create a hash of possible params: (CGI param name => table column name)
$params = array(
'first_name' => 'first_name',
'last_name' => 'last_name',
);
It's much better from a security point of view as you are protected from SQL injection.
Then get the column name from the hash and put it into the query - and you'll get rid of the if-s:
$name = $params[$param];
$sql = "SELECT * FROM table
WHERE
$name LIKE ?";
if($prep = $link->prepare($sql))
{
$prep->bind_param('s', "%$search%");
...
As @Akam said, no need to CONCAT("%", ?, "%") in the query - it's better to bind the value with percents right ahead.
According to this example in the PHP manual
http://www.php.net/manual/en/mysqli-stmt.bind-param.php#108790
You seem to be best to add the '%' to the string variable you are binding - rather than in the query e.g. in your example:
if($prep = $link->prepare('SELECT * FROM table
WHERE first_name
LIKE ?'))
{
$search='%'.$search.'%';
$prep->bind_param('s', $search);
$prep->execute();
$prep->bind_result($first_name, $last_name);
while($prep->fetch())
echo $first_name . ' ' . $last_name;
$prep->close();
}
Haven't tested it but it seems like a sensible solution.
You can't use placeholders for column names, so you'll have to concatenate the column names normally. However, instead of just escaping it with mysqli, because you have a limited set of columns I'd suggest a while list approach:
$allowed_params = array('first_name', 'last_name', etc);
$param = $_POST['param' ];
$search = $_POST['search'];
if(!in_array($param, $allowed_params))
die("Uh oh, the request seems to have an invalid param!");
if($prep = $link->prepare('SELECT * FROM table
WHERE ' . $param . '
LIKE ?'))
{
$prep->bind_param('s', '%' . $search . '%');
$prep->execute();
$prep->bind_result($first_name, $last_name);
while($prep->fetch())
echo $first_name . ' ' . $last_name;
$prep->close();
}
else
echo 'Error while preparing statement.';
Also note the removal of the concat
statement, and instead concatenating in PHP. This is important for prepared statements as once it gets to the server it's not actually combining them (thus the protection of prepared statements), so it won't work properly unless the wildcards are sent with the $search
string.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With