User inputs, clean and sanitize before sending to db - php

I've searched a lot of the questions here and I found that they either very old or suggesting using prepared statements PDO which I am not using. So I need your help please.
I have a small discussion/chat box where a user submit a message using a <textarea>
What I need is sanitize and filter the user input so it only accepts plain texts (e.g. no tags, no html tags, no scripts no links, etc). Also, it is important to allow line breaks.
Based on my reading I am doing the following in the following order:
trim()
htmlentities($comment, ENT_NOQUOTES)
mysqli_real_escape_string()
nl2br()
Is what I am doing is right? or I am missing something?
Also is there anything I have to do when echoing the data from the db?
really, appreciate your help and kindness

First, keep the text logical and clean:
trim() -- OK
htmlentities($comment, ENT_NOQUOTES) -- No; do later
mysqli_real_escape_string() -- Yes; required by API
nl2br() -- No; see below
The logic behind those recommendations: The data in the database should be just plain data. Not htmlentities, not br-tags. But, you must do the escape_string in order to pass data from PHP to MySQL; the escapes will not be stored.
But... That is only the middle step. Where did the data come from? Older versions of PHP try to "protect" you be adding escapes and other junk that works OK for HTML, but screws up MySQL. Turn off such magic escaping, and get the raw data.
Where does the data go to? Probably HTML? After SELECTing the data back out of the table, then first do htmlentities() and (optionally) nl2br();
Note, if you are expecting to preserve things like <I> (for italic), you are asking for trouble -- big trouble. All a hacker needs to do is <script> ... to inject all sorts of nastiness into your web page and possibly your entire system.

You also have another option. You can use prepared statements with mysqli
They aren't very difficult to learn and work a bit better than mysqli_real_escape_string() in that you don't need to worry about escaping every single variable that will be in your query. They are by nature "prepared" before they go into the database. There are other advantages to this as well, in that:
you do not need to addslashes() to be able to handle characters with
apostrophes etc.
for large databases, they will considerably speed
up your queries (much like PDO).
Here's how to do it:
You connect to the database by creating a new mysqli object like this:
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $dbc->connect_error);
}
Next you want to convert your variables from your form.
Say you have a form field like this:
<input type="text" name="var1">
you can use htmlentities and trim together like so, and create your $var1 variable:
$var1 = htmlentities(trim($_POST['var1']));
Then you can create your transaction like this:
$stmt= $conn->prepare("insert into tablename (key1, key2) values (?,?)");
$stmt->bind_param("is",$var1, $var2);
$stmt->execute();
$stmt->close();
That's basically it. You do a query just like you normally would, but instead use the ? placeholders, assigning the datatype (above is i for integer, and s for string) and then bind them to your placeholders in the query.
That's basically it.
if you want to do it with a select with a variable, you use the normal select syntax and the same way with a ? with the variable, and then bind it. You can then bind your results into variables easily like so (assuming var3 is an integer):
$stmt= $conn->prepare("select var1, var2 from tablename where var3 = ?");
$stmt = bind_param("i", $var3);
$stmt->bind_result($var1, $var2);
$stmt->execute();
$stmt->close()
and then you can fetch your variables using this
$stmt->fetch();
or if your query brings back multiple rows
while ($stmt->fetch() {
echo $var1 . $var2;
}
nl2br() is used for output, you don't need to worry about input; it can be stored in the database as \n, and when you need it spits it out as breaks. If one of these variables needs the new lines turned into <br/> tags, you can, as you suggest use nl2br() on the variables (note this adds no security, but as you said you needed it), like so
echo nl2br($var1, false);
you can also use trim() and htmlentities() on this if it is being echoed into, say, a form input field and you don't want your form to break if there are html characters in the output.

Your question can lead me to build a full project with many features ;) lol
Before we start with out steps, we need a dummy (test) database for this scenario. We call out database chatbox with table called chat. You can simply create it by executing the following sql statement in your MySQL test environment:
CREATE TABLE `chat` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`msg` VARCHAR(200) NOT NULL DEFAULT '0',
`user_id` INT(11) NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
;
Now you can go a head and follow the steps here:
Step 1: Create project folder in your web server.
Build database connection based on PDO and call it dbConnect.inc.php:
<?php
// check if PDO driver not available
if (!defined('PDO::ATTR_DRIVER_NAME'))
echo 'PDO driver unavailable <br />';
// database configuration
$dbHost = "localhost";
$dbPort = "3306";
$dbName = "chatbox";
$dbUser = "root";
$dbPass = "";
$strDSN = "mysql:host=$dbHost:$dbPort;dbname=$dbName";
// database connection
try
{
$dbConn = new PDO($strDSN, $dbUser, $dbPass);
//Activate following line to view all error messages
$dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e)
{
die("Could not connect to the database $dbName, error info: <br />"
. $e->getMessage());
exit();
}
I will test this works before go to next step. Btw the prepared method does not require mysqli_real_escape_string().
I have used PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION in stead of if statements, this method will give you useful error details while development the project. You will find out which method is more practical for getting error message while your development process of the project.
Step2: Create a file call filter.inc.php:
<?php
// filter for input
function filterInput($content)
{
$content = trim($content);
$content = stripslashes($content);
return $content;
}
//filter for viewing data
function filterOutput($content)
{
$content = htmlentities($content, ENT_NOQUOTES);
$content = nl2br($content, false);
return $content;
}
This file contain a function to filterInput to sanitize or filter your input content for comments or other inputs. And filterOutput that effect your data view.
All depending on your strategy and what you need, like if you need to allow people post url's or email address, should url and email become active link or only viewed as text etc. that way you can define which filter should be use for your content input and which filter should be used for you content output.
You can add or delete extra features to functions. There are many features for text input and output, you can test those individually and evaluate it, and even extend the filter function or create your own function.
Final step 3: Now we put the puzzles together in our index.php file:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Chat box</title>
</head>
<body>
<?php include './dbConnect.inc.php'; ?>
<?php include './filter.inc.php'; ?>
<h1>Chat box</h1>
<p>
<?php
// this is dummy user id, but use the id over user id when login or the way you want
// this is only example
$user_id = 1;
if (isset($_POST["msg"]))
{
$msg = filterInput($_POST["msg"]);
$sql = "INSERT INTO chat "
. "(msg, user_id) "
. "VALUES "
. "(:msg, :user_id)";
$stmt = $dbConn->prepare($sql);
$fieldsArr = [':msg' => $msg, ':user_id' => $user_id];
$stmt->execute($fieldsArr)
// refresh page after insert
header("Location: " . $_SERVER['REQUEST_URI']);
}
?>
<form action="index.php" method="post">
<textarea name="msg" id="msg" required></textarea>
<input name="submit" type="submit">
</form>
</p>
<p>Comments</p>
<p>
<?php
$sql = "SELECT * FROM chat WHERE user_id = (:user_id);";
$stmt = $dbConn->prepare($sql);
$fieldsArr = [':user_id' => $user_id];
$stmt->execute($fieldsArr)
while ($result = $stmt->fetch())
echo "<h3>" . filterOutput($result['msg']) . "</h3>";
$dbConn = null;
?>
</p>
</body>
</html>
This is to demonstrate how things works. You have insert, select statement as example and filter functions. You can make tests, extend it the way you like or further develop your own project.
Here is screen shot of the chatbox example I made:

filter_input could be another one you are looking for. It can save you hours from writing sanitizing and validation code. Of course, it does not cover every single case, but there is enough so that you can focus more on specific filtering/validating code.
Though it is strongly recommended to use prepared statements with
PDO/mysqli. But sometimes it is not so easy to convert the whole
project in the tail end of the project. You should learn PDO/mysqli for
your next project.
$comment = filter_input(INPUT_POST, 'comment', FILTER_SANITIZE_STRING);
There are different Types of filters for you. You can select depending on your needs. You can also use filter_has_var to check for variable set.

Your code looks fine, if you don't want to prepare statements then escaping is the next best thing. And when you echo it should be straightforward, it's only plain text.

Related

Data from table won't show up if <script> is in the string. Stops execution

So I inputted data into my table using a form, and used
$username = mysqli_real_escape_string($con, $_POST['username']);
the mysqli_real_escape_string to work around SQL injections. Using this method will result the data to be saved like this, in the case of someone trying to use a script as an input <script> some bad script </script>
and surely the injection won't happen, but I noticed that while trying to retrieve data from the table, the execution will stop once it reaches the username that is saved like that. I tried editing the table and removing the <script> </script> parts and refreshed the page, and as expected the execution didn't stop and finished properly. I used the following code to retrieve the data from the table
require_once ('dbconnect.php');
$title= "The App";
$qry = "SELECT * FROM businesses ";
$result = mysqli_query($con,$qry);
$info = array();
while($row_info = mysqli_fetch_assoc($result))
{ $info= $row_info;
echo " The ID:" . $row_info['buss_id'] . "<br>" . "The username:" . $row_info['username'] . "<br>";
}
Note: dbconnect.php has the $con variable. Thank you.
I would also like to ask about the part in the code where it says
$info=$row_info;
I was following a tutorial and they did it that way, however clearly I could have the same result by using the $row_info variable. Any clarification would be appreciated. Thanks.
If I'm not mistaken, you're talking about removing javascripts from form input (e.g. attempt to save malicious javascript to your forum / message board so you will display them to all other visitors).
Both mysqli_real_escape_string and prepared statements only work against SQL injections, (i.e. attempt to trick your program to run SQL statement that is not supposed to run). Of cause you should use them in your form input. But they are security on a different abstraction level than your question concerned.
You'd need to do HTML sanitation to your text input before saving to your database, or before displaying them to anyone. You should
use strip_tags on the string; or
use filter_var with appropriate Sanitize Filter; or
If you do not wish to have HTML for your user input, you may use htmlspecialchar to convert them into HTML-save plain text.
Before insert to database
$text = addslashes(htmlspecialchars($text));
Output
$text = stripslashes(trim(implode('',explode('\\',$text))));
echo $text;
This should works fine

PHP announcements system errors

I want to make a simple announcements system where you just type announcements in a box and it can be viewed by others. I get this error when I submit the form:
Edit:
Thanks, the php_announce.php now works, it does everything it's supposed to do, this is the new code :
<?php
// MySQL
$servername = "localhost";
$username = "testuser";
$password = "testpass";
$dbname = "testbase";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// Content
$content = $conn->real_escape_string($_POST['content']);
$query="INSERT INTO announcements (content) VALUES ('$content')";
$conn->query($query)
?>
Edit: Thanks for the help I fixed the code like this:
after creating connection it does this:
$result=$conn->query("SELECT * FROM announcements");
#print_r($result);
while($row = $result->fetch_array(MYSQLI_ASSOC)){
echo $row['content']. " <br><br> ". "<b>Posted by: You</b>";
echo "<hr width=100%>";
}
In the first case, you need to provide a list of columns that correspond to the values you're trying to insert. e.g:
$query="INSERT INTO announcements (content) VALUES ('$content')";
Replace content with the name of the column in your table.
For the second one, the error message doesn't appear to correspond with the code you posted, but I'm guessing you forgot a semicolon on the preceding statement. Additionally, there's a missing ; in your last echo statement, although I'm not sure if that's what's causing the specific message you posted. Please repost the latest code from announce_out.php and I'll try to help.
There are some other issues with this approach in general. You're mixing mysqli and the older mysql, which is going to cause you additional errors. Instead of calling mysql_query(), you should be using $conn->query($query) so that you're actually using the connection you are establishing with new mysqli(). Additionally, to prevent injection attacks, you should escape your $content variable in this way:
$content = $conn->real_escape_string($_POST['content']);
This is a pretty basic way to escape strings and there are better methods like prepared statements that mysqli provides. I recommend checking out the page in the PHP manual: http://php.net/manual/en/mysqli.quickstart.php
The immediate answer to your problem is:
You need to escape the content and let the function quote it for you.
$content= mysql_real_escape_string($_POST['content'], $conn);
$query="INSERT INTO announcements VALUES ($content)";
and you need a missing semicolon at the end of the echo statement.
echo $row['content']. " <br><br> ". "<b>Posted by: You</b>";
The long term answer is you should really look into a database abstraction layers which help you avoid incredibly dangerous errors you're likely to make (like incorrectly escaping content). See php manual for some basic ones: http://php.net/manual/en/refs.database.abstract.php
But I also highly recommend using a higher level framework like Doctrine http://www.doctrine-project.org/.

"&" And "And" In A Prepared Statement Failing

I have this chunk of PHP code (abridged for brevity) as a prepared statement that allows the user to search through a database using a series title...
$series = null;
if (isset($_GET["series"])) $series = $_GET["series"];
try {
$dbh = new PDO("sqlsrv:Server=localhost;Database=Radio", "", "");
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if (isset($series)) {
$sql = "SELECT *
FROM dbo.Schedule
WHERE dbo.Schedule.Series LIKE :series
ORDER BY dbo.Schedule.Date ASC";
}
$stmt = $dbh->prepare($sql);
if (isset($series)) {
$stmt->execute(array(":series" => $series));
}
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$dbh = null;
}
catch(PDOException $e) {
echo $e->getMessage();
}
It works perfectly fine for any series title that DOES NOT include &, but it fails (produces no results) for any series title that does.
For instance, Abounding Ability works but Receiving & Ministering Healing does not (even though both are in the Series field of the database.
I tried htmlspecialchars(rawurlencode($_GET["series"])) and each one individually.
On the SQL server, passing the same query (using the title) works just fine.
SELECT *
FROM [Radio].[dbo].[Schedule]
where Series like 'Receiving & Ministering Healing'
My guess is that the problem is in the execution of the prepared statement or in binding the parameters.
I did a search on PHP's Prepared Statements page for &, but didn't see any results.
Edit:
I used this to get it working...
$series = null;
if (isset($_GET["series"])) $series = $_GET["series"];
$queries = parse_url($_SERVER['QUERY_STRING']);
if (substr($queries["path"], 7) == "Receiving%20&%20Ministering%20Healing") $series = "Receiving & Ministering Healing";
I'm sure it's a terrible way to do it, but no other option was working. Of course, that only works in this particular case. I welcome a better solution.
Edit 2:
I found that a series with the WORD And in it (Graces And Places) presents the same issue. I ended up duplicating my "fix" for Receiving & Ministering Healing for it.
I doesn't seem to have anything to do with the URL query (or the word And wouldn't matter), but the prepared statement.
There can be multiple causes for this. The & character is evil on multiple levels, in this case, these two seem relevant:
It can be that your request looks like this (& signs highlighted under)
http://somedomain.com/somepage.php?param=something&series=Receiving & Ministering Healing
^ (valid) ^ (unintended!)
because the title is not escaped for URLs. This would mean that the & character marks a new parameter name - and would truncate the series to be Receiving instead of the full title Receiving & Ministering, resulting in the query:
SELECT *
FROM [Radio].[dbo].[Schedule]
where Series like 'Receiving '
You should probably show us how you assemble the URL for the request to be able to recommend more approporiate solution, but you should use urlencode() for escaping values to be used in GET parameters.
Or the problem could be (however it is not this case now) that you are writing the values wrongly.
A single & character is not valid in HTML! The correct escaping is &
You should HTML encode the output from the SELECT queries with htmlentities:
echo "<h1>".htmlentities($resultComingFromQuery)."</h1>";
EDIT
A bad request will likely result in a bad response (GIGO)... This:
http://www.flcbranson.org/api/radio/?series=Receiving%20%26%20Ministering Healing
is the proper request. This is URL encoded. You have to decode it:
$decodedTitle = urldecode($_GET['series']);
And use that in the query...

Insert mysql error when parsing a webpage

Hi when ever I want to insert a comment into my database, I sanitize the data by using Mysql Escape String function this however inserts the following verbatim in field. I print the comment and it works fine and show me the text however when ever I sanitize it, it literally inserts the following into my db
mysql_real_escape_string(Comment)
This is my insert statement, The Id inserts correctly however the comment doesn't it just inserts the "mysql_real_escape_string(Comment)" into the field. what can be wrong?
foreach($html->find("div[class=comment]") as $content){
$comment = $content->plaintext;
$username = mysql_real_escape_string($comment);
$querytwo = "insert into Tchild(Tid,Tcomment)values('$id','$username')";
$resulttwo = $db -> Execute($querytwo);
}
If I'm reading the documentation correctly, you should make the call like this:
$db->Execute("insert into Tchild(Tid,Tcomment)values(?, ?)", array($id, $username));
That will account for proper escaping. Having unescaped values in your query string is dangerous and should be avoided whenever possible. As your database layer has support for SQL placeholders like ? you should make full use of those any time you're placing data in your query.
A call to mysql_real_escape_string will not work unless you're using mysql_query. It needs a connection to a MySQL database to function properly.
Since you're using ADODB, what you want is probably $db->qstr(). For example:
$username = $db->qstr($comment, get_magic_quotes_gpc());
See this page for more information: http://phplens.com/lens/adodb/docs-adodb.htm

Replacing mysql_* functions with PDO and prepared statements

I've always done the simple connection of mysql_connect, mysql_pconnect:
$db = mysql_pconnect('*host*', '*user*', '*pass*');
if (!$db) {
echo("<strong>Error:</strong> Could not connect to the database!");
exit;
}
mysql_select_db('*database*');
While using this I've always used the simple method to escape any data before making a query, whether that be INSERT, SELECT, UPDATE or DELETE by using mysql_real_escape_string
$name = $_POST['name'];
$name = mysql_real_escape_string($name);
$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error());
Now I understand this is safe, to an extent!
It escapes dangerous characters; however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
So, I searched a little bit and found out about PDO, MySQLi and prepared statements. Yes, I may be late to the game but I've read many, many tutorials (tizag, W3C, blogs, Google searches) out there and not a single one has mentioned these. It seems very strange as to why, as just escaping user input really isn't secure and not good practice to say the least. Yes, I'm aware you could use Regex to tackle it, but still, I'm pretty sure that's not enough?
It is to my understanding that using PDO/prepared statements is a much safer way to store and retrieve data from a database when the variables are given by user input. The only trouble is, the switch over (especially after being very stuck in my ways/habits of previous coding) is a little difficult.
Right now I understand that to connect to my database using PDO I would use
$hostname = '*host*';
$username = '*user*';
$password = '*pass*';
$database = '*database*'
$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password);
if ($dbh) {
echo 'Connected to database';
} else {
echo 'Could not connect to database';
}
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work. So I'm having to read/remember a load of new ones, but this is where I'm getting confused.
If I wanted to insert data from say a sign up/registration form, how would I go about doing this, but mainly how would I go about it securely? I assume this is where prepared statements come in, but by using them does this eliminate the need to use something like mysql_real_escape_string? I know that mysql_real_escape_string requires you to be connected to a database via mysql_connect/mysql_pconnect so now we aren't using either won't this function just produce an error?
I've seen different ways to approach the PDO method too, for example, I've seen :variable and ? as what I think are known as place holders (sorry if that is wrong).
But I think this is roughly the idea of what should be done to fetch a user from a database
$user_id = $_GET['id']; // For example from a URL query string
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
But then I'm stuck on a couple things, if the variable wasn't a number and was a string of text, you have to given a length after PDO:PARAM_STR if I'm not mistaken. But how can you give a set length if you're not sure on the value given from user in-putted data, it can vary each time? Either way, as far as I know to display the data you then do
$stmt->execute();
$result = $stmt->fetchAll();
// Either
foreach($result as $row) {
echo $row['user_id'].'<br />';
echo $row['user_name'].'<br />';
echo $row['user_email'];
}
// Or
foreach($result as $row) {
$user_id = $row['user_id'];
$user_name = $row['user_name'];
$user_email = $row['user_email'];
}
echo("".$user_id."<br />".$user_name."<br />".$user_email."");
Now, is this all safe?
If I am right, would inserting data be the same for example:
$username = $_POST['username'];
$email = $_POST['email'];
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?);
$stmt->execute();
Would that work, and is that safe too? If it is right what value would I put in for the ?_LENGTH_?? Have I got this all completely wrong?
UPDATE
The replies I've had so far have been extremely helpful, can't thank you guys enough! Everyone has got a +1 for opening my eyes up to something a little different. It's difficult to choose the top answer, but I think Col. Shrapnel deserves it as everything is pretty much covered, even going into other arrays with custom libraries which I wasn't aware of!
But thanks to all of you:)
Thanks for the interesting question. Here you go:
It escapes dangerous characters,
Your concept is utterly wrong.
In fact "dangerous characters" is a myth, there are none.
And mysql_real_escape_string escaping but merely a string delimiters. From this definition you can conclude it's limitations - it works only for strings.
however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.
You're mixing here everything.
Speaking of database,
for the strings it is NOT vulnerable. As long as your strings being quoted and escaped, they cannot "modify or delete data maliciously".*
for the other data typedata - yes, it's useless. But not because it is somewhat "unsafe" but just because of improper use.
As for the displaying data, I suppose it is offtopic in the PDO related question, as PDO has nothing to do with displaying data either.
escaping user input
^^^ Another delusion to be noted!
a user input has absolutely nothing to do with escaping. As you can learn from the former definition, you have to escape strings, not whatever "user input". So, again:
you have escape strings, no matter of their source
it is useless to escape other types of data, no matter of the source.
Got the point?
Now, I hope you understand the limitations of escaping as well as the "dangerous characters" misconception.
It is to my understanding that using PDO/prepared statements is a much safer
Not really.
In fact, there are four different query parts which we can add to it dynamically:
a string
a number
an identifier
a syntax keyword.
so, you can see that escaping covers only one issue. (but of course, if you treat numbers as strings (putting them in quotes), when applicable, you can make them safe as well)
while prepared statements cover - ugh - whole 2 isues! A big deal ;-)
For the other 2 issues see my earlier answer, In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?
Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work.
That is another, grave delusion of PHP users, a natural disaster, a catastrophe:
Even when utilizing old mysql driver, one should never use bare API functions in their code! One have to put them in some library function for the everyday usage! (Not as a some magic rite but just to make the code shorter, less repetitive, error-proof, more consistent and readable).
The same goes for the PDO as well!
Now on with your question again.
but by using them does this eliminate the need to use something like mysql_real_escape_string?
YES.
But I think this is roughly the idea of what should be done to fetch a user from a database
Not to fetch, but to add a whatever data to the query!
you have to given a length after PDO:PARAM_STR if I'm not mistaken
You can, but you don't have to.
Now, is this all safe?
In terms of database safety there are just no weak spots in this code. Nothing to secure here.
for the displaying security - just search this site for the XSS keyword.
Hope I shed some light on the matter.
BTW, for the long inserts you can make some use of the function I wrote someday, Insert/update helper function using PDO
However, I am not using prepared statements at the moment, as I prefer my home-brewed placeholders over them, utilizing a library I mentioned above. So, to counter the code posted by the riha below, it would be as short as these 2 lines:
$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
$data = $db->getRow($sql,$_GET['name'],'admin',1);
But of course you can have the same code using prepared statements as well.
* (yes I am aware of the Schiflett's scaring tales)
I never bother with bindParam() or param types or lengths.
I just pass an array of parameter values to execute(), like this:
$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
$stmt->execute( array(':user_id' => $user_id) );
$stmt = $dbh->prepare("INSERT INTO `users` (username, email)
VALUES (:username, :email)");
$stmt->execute( array(':username'=>$username, ':email'=>$email) );
This is just as effective, and easier to code.
You may also be interested in my presentation SQL Injection Myths and Fallacies, or my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Yes, :something is a named placeholder in PDO, ? is an anonymous placeholder. They allow you to either bind values one by one or all at once.
So, basically that makes four options to provide your query with values.
One by one with bindValue()
This binds a concrete value to your placeholder as soon as you call it. You may even bind hard coded strings like bindValue(':something', 'foo') if desired.
Providing a parameter type is optional (but suggested). However, since the default is PDO::PARAM_STR, you only need to specify it when it is not a string. Also, PDO will take care of the length here - there is no length parameter.
$sql = '
SELECT *
FROM `users`
WHERE
`name` LIKE :name
AND `type` = :type
AND `active` = :active
';
$stm = $db->prepare($sql);
$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
$stm->bindValue(':active', 1, PDO::PARAM_INT);
$stm->execute();
...
I usually prefer this approach. I find it the cleanest and most flexible.
One by one with bindParam()
A variable is bound to your placeholder that will be read when the query is executed, NOT when bindParam() is called. That may or may not be what you want. It comes in handy when you want to repeatedly execute your query with different values.
$sql = 'SELECT * FROM `users` WHERE `id` = :id';
$stm = $db->prepare($sql);
$id = 0;
$stm->bindParam(':id', $id, PDO::PARAM_INT);
$userids = array(2, 7, 8, 9, 10);
foreach ($userids as $userid) {
$id = $userid;
$stm->execute();
...
}
You only prepare and bind once which safes CPU cycles. :)
All at once with named placeholders
You just drop in an array to execute(). Each key is a named placeholder in your query (see Bill Karwins answer). The order of the array is not important.
On a side note: With this approach you cannot provide PDO with data type hints (PDO::PARAM_INT etc.). AFAIK, PDO tries to guess.
All at once with anonymous placeholders
You also drop in an array to execute(), but it is numerically indexed (has no string keys). The values will replace your anonymous placeholders one by one in the order they appear in your query/array - first array value replaces first placeholder and so forth. See erm410's answer.
As with the array and named placeholders, you cannot provide data type hints.
What they have in common
All of those require you to bind/provide as much values as you have
placeholders. If you bind too many/few, PDO will eat your children.
You don't have to take care about escaping, PDO handles that. Prepared PDO statements are SQL injection safe by design. However, that's not true for exec() and query() - you should generally only use those two for hardcoded queries.
Also be aware that PDO throws exceptions. Those could reveal potentially sensitive information to the user. You should at least put your initial PDO setup in a try/catch block!
If you don't want it to throw Exceptions later on, you can set the error mode to warning.
try {
$db = new PDO(...);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
} catch (PDOException $e) {
echo 'Oops, something went wrong with the database connection.';
}
To answer the length question, specifying it is optional unless the param you are binding is an OUT parameter from a stored procedure, so in most cases you can safely omit it.
As far as safety goes, escaping is done behind the scenes when you bind the parameters. This is possible because you had to create a database connection when you created the object. You are also protected from SQL injection attacks since by preparing the statement, you are telling your database the format of the statement before user input can get anywhere near to it. An example:
$id = '1; MALICIOUS second STATEMENT';
mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1
and the executes the
malicious second statement */
$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a
single statement with
a single parameter */
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second
STATEMENT' i.e. returns empty set. */
Thus, in terms of safety, your examples above seem fine.
Finally, I agree that binding parameters individually is tedious and is just as effectively done with an array passed to PDOStatement->execute() (see http://www.php.net/manual/en/pdostatement.execute.php).

Categories