I'm creating a function to get stats on different players from a database and I need to be able to specify the timescale the stats are for as one of the function's variables - it'll be something like 'month' or 'year'.
I've had some success in creating SQL queries using EXTRACT() to compare the record timestamp with the current time but I can't seem to use a PDO variable to change this with each function call.
Any ideas what I'm doing wrong? Or is this just a limitation of bindParam()?
function get_player_stats($player_id, $timescale) {
global $pdo;
$query = $pdo->prepare('
SELECT count(results.winner)
FROM results
WHERE results.winner = :player_id
AND EXTRACT(:timescale FROM results.date) = EXTRACT(:timescale FROM NOW())
LIMIT 1
');
$query->bindParam(':player_id', $player_id);
if ($timescale = 'this_month') {
$query->bindParam(':timescale','YEAR_MONTH');
}
else if ($timescale = 'this_year') {
$query->bindParam(':timescale','YEAR');
}
$query->execute();
return $query->fetchAll(PDO::FETCH_OBJ);
}
P.S. It's a MySQL database
I see two problems here:
The EXTRACT() function doesn't expect a string but an identifier. You cannot use bind parameters to provide identifiers (such as table names).
Repeating place-holders is not supported by all PDO drivers and modes thus it's discouraged.
You'll have to inject the value into the SQL code with your favourite string manipulation technique.
From the PHP PDO docs:
You cannot use a named parameter marker of the same name twice in a prepared statement.
I read somewhere that old versions of PHP allowed you to put one placeholder in multiple parts of the query, however this feature has been removed in newer versions of PHP. You'll need to give each placeholder different names in your query.
Related
I have a user table with and id field and 10 other fields storing user details of various types that the user can change via various web forms. I want to have a PHP script that gets POSTed changed values for some subset of these fields, and UPDATEs only those fields that are received in the POST data. I'm finding this surprisingly difficult to do in a way that doesn't suck. We use mysqli for all database interaction in the rest of this application so mysqli-based solutions are strongly preferred.
The options I've considered and dismissed so far:
1) Run a separate UPDATE query for every field provided in the POST data - yuck, I don't want to hit the database up to 10 times for something that could be done in one query.
2) Have a dictionary mapping field names to the fields' data types, and iteratively construct the query by looping through the provided fields, checking whether they are text fields or not, calling mysqli_real_escape_string on the string fields and otherwise sanitizing the others (e.g. by type checking or sprintf with '%i' placeholders). - Yuck! I could probably safely do things this way if I was careful, but I don't want to make a habit of using this kind of approach because if I'm careless I'll leave myself open to SQL injection. Parameterized queries don't give me the potential to screw up dangerously, but this approach does. My ideal is to never concatenate any data into an SQL query manually and always rely upon parameterized queries; the database libraries of other languages, like Python, let me easily do this.
3) Use a parameterized query - this is my ideal for everything, since as long as I insert all externally-provided data into my query via the bind_param method of a mysqli statement object, I'm immune to SQL injection and don't have to worry about sanitization, but using parameterized queries seems to be impossible here. The trouble is that bind_param requires that the data be passed as variables, since all arguments after the first are passed by reference. I can reasonably elegantly iteratively construct a query with ? placeholders, and while I'm at it construct the string of types that gets passed as the first argument to bind_param ('ssiddsi' etc.), but then there's no way I can see to choose at runtime which of my 10 fields I pass to bind_params (unless I have a switch statement with 10^2 cases).
Is there some PHP language construct I'm missing (something similar to array unpacking) that will allow me to choose at runtime which variables to pass as arguments to bind_param? Or is there some other approach I haven't considered that will let me solve this simple problem cleanly and safely?
You can easily combine 2 and 3 by means of my SafeMySQL library.
The code will look like
$allowed = array('title','url','body','rating','term','type');
$data = $db->filterArray($_POST,$allowed);
$sql = "UPDATE table SET ?u WHERE id=?i";
$db->query($sql, $data, $_POST['id']);
note that $allowed array doesn't make all these fields necessarily updated - it just filters POST fields out. So, even $_POST with only id and url would be correctly updated.
Nevertheless, using prepared statements, although toilsome, also quite possible.
See the code below
public function update($data, $table, $where) {
$data_str = '' ;
foreach ($data as $column => $value) {
//append comma each time after first item
if (!empty($data_str)) $data_str .= ', ' ;
$data_str .= "$column = $value" ;
}
$sql = "UPDATE $table SET $data_str WHERE $where";
mysqli_query($sql) or die(mysqli_error());
return true;
}
$data is an array, in your case it's $_POST.
If you want to be more specific about the data to be saved from $_POST array, you can define an array of allowed columns. For example,
$allowed = array('id', 'username', 'email', 'password');
By doing this, you can filter your $_POST array and pass it to update() function.
I'm building simple query builder, and I have two questions:
Is it possible to secure mysql queries with normal functions to the similar level as it is done using ->execute(array(':param:' => ... ?
Is it possible to use many variables in one query, give them the same names (the ones after the semicolon), and then bind them one by one?
If I understand you correctly, you would like to know if it possible to replicate the functionality of bindParam with the standard mysql_* functions?
Short answer is no. Please do not use the mysql functions at all, use mysqli or PDO as these provide you with the true security when it comes to prepared statements. They can also provide much better query performance as the SQL is able to be pre-optimised for the database.
You will have to define each parameter separately (even if it is the same value). You could also pass a simple array to the execute() method call, but you do not then have the option to explicitly define the parameter types.
Within your function use some thing like this:
$name = "fred";
$statement = $pdo->prepare("SELECT id FROM contacts WHERE first_name = ? OR last_name = ?");
for ($x = 1; $x <= 2; $x++) {
$statement->bindParam($x, $name, PDO::PARAM_STR);
}
$statement->execute();
I'm learning PDO, and finding it tricky to make sure my statements work correctly. I have a PHP function which is updating my database by simply adding the number 1 to the total.
function add_rating($place_id,$rating_id) {
//make $db accessible inside the function
global $db;
// query v1
$sql = "UPDATE places_ratings SET ? +1 WHERE place_id=?";
$q = $db->prepare($sql);
$q->execute(array($rating_id,$place_id));
}
I tried variations of this, none of which I could get to work. I don't know if I was using question marks wrong. I was following this guide and also a previous SO question. In the end I tried a different method which worked first time, so I am tempted to re-use it as it also seems a lot simpler.
function add_rating($place_id,$rating_id) {
//make $db accessible inside the function
global $db;
// query v2
$query = "UPDATE places_ratings SET $rating_id = ($rating_id +1) WHERE place_id = $place_id";
$update = $db->query($query);
}
My question is: which statement is better/safer? And secondly, what am I doing wrong with the first version with question marks? Thanks...
In general prepared statements as in your first example are safer because they are immune to SQL injection.
Your example doesn't work because you can't specify field names using a ? parameter in a prepared statement. Even if you could your SQL still would be wrong, this would expand to
UPDATE places_ratings SET whatever +1 WHERE place_id=?
which is not valid.
If your $rating_id is generated in code and not taken from user input you could combine both approaches.
Prepared statements are not simply like copy'n'pasting variables into a piece of text. Prepared statements separate between the query logic and the values the query should work on. They're there so you're able to tell your database "You're supposed to do this", let the database understand it, then give it the values it's supposed to do that something with. The logic itself cannot be variable, it needs to be complete the first time.
Therefore, you can only use placeholders for values. Your query needs to read UPDATE ... SET FIELD = VALUE WHERE FIELD = VALUE. The FIELD parts need to be in the statement, the VALUE parts you can use placeholders for. It looks like your $rating_id variable is a variable field name. First of all, that's a bad idea. You should not make field names variable if possible. But if you have to, you cannot use prepared statement placeholders for them. Instead, you'll have to do it like this:
$rating_id = 'field_name';
$query = "UPDATE places_ratings SET `$rating_id` = `$rating_id` + 1 WHERE `place_id` = ?";
$stmt = $db->prepare($query);
$stmt->execute(array($place_id));
It's up to you to make sure $rating_id is safe and contains known, whitelisted values. Don't let the user supply the value for it in any way.
Please, go an learn what prepared statements are. And you could also use a tutorial, that does not promote bad practices and vulnerable code.
A correctly created and used prepared statement will always be more secure then concatenated query string, because prepared statements send query logic and data separately.
Also , if you are using PDO, then quite often the use of bindParam() method should be preferred over passing the values directly in the execute() method as an array. This is because, when passing values in execute(), the values are bound as PDO::PARAM_STR, even if DB column expects and integer.
P.S. Stop using global in your code !!
PDO always returns field values as strings when using MySQL. Is PDO consistent when using another database like MSSQL?
If not, is there a flag which forces PDO to always return strings (for purpose of consistency)? or better still to return native types for all values?
From what I can tell, Drupal makes it possible to use different databases using PDO. It performs the necessary conversions to make SQL statements compatible with the varying syntaxes. But how does it deal with data types in query results?
If you want to make sure that you always get strings you can use bindColumn() and specify the data type for each column
$sql = 'SELECT id, name FROM test';
$stmt = $dbh->query($sql);
/* Bind by column number */
$stmt->bindColumn(1, $id, PDO::PARAM_STR); //or PDO::PARAM_INT
$stmt->bindColumn(2, $name, PDO::PARAM_STR);
while ($row = $stmt->fetch(PDO::FETCH_BOUND)) {
var_dump($id); var_dump($name);
}
In so far as I remember, this depends on the DB engine.
Some time ago, PDO would return a t or f strings for boolean fields in Postgres, and I vaguely recall that it was returning a true or false boolean the last time I used it.
You can normalize results into native types after checking getColumnMeta():
http://us3.php.net/manual/en/pdostatement.getcolumnmeta.php
Doing so comes with a few strings attached, though. The warnings in the php manual are one. The inconsistent values returned from an engine to the next are another:
List of PHP native_type's for PDO getColumnMeta()
my goal here is to be able to get a variable (with php) and use it in a prepared statement (with mysqli), and then fetch_assoc. For some reason this code will not work (no errors). I've rtm and I haven't found anything combining fetch_assoc with prepared statements, so I'm not sure if it's even possible. Any help to get this working is appreciated, here's my code currently.
$where = $_GET['section'];
$mysqli = mysqli_connect("localhost", "root", "","test");
if($stmt = mysqli_prepare($mysqli,"SELECT title, img, active, price FROM ? ORDER by ID limit 5 ")){
mysqli_stmt_bind_param($stmt, 's', $where);
mysqli_stmt_execute($mysqli);
mysqli_stmt_fetch($mysqli);
while($row = mysqli_fetch_assoc($stmt)){
if($row['active']=="yes"){
echo 'the rest of my stuff goes here';
From the PHP website page for mysqli->prepare (with emphasis added to the most relevant part):
Note:
The markers are legal only in certain places in SQL statements. For
example, they are allowed in the VALUES() list of an INSERT statement
(to specify column values for a row), or in a comparison with a column
in a WHERE clause to specify a comparison value.
However, they are not allowed for identifiers (such as table or column
names), in the select list that names the columns to be returned by a
SELECT statement), or to specify both operands of a binary operator
such as the = equal sign. The latter restriction is necessary because
it would be impossible to determine the parameter type. In general,
parameters are legal only in Data Manipulation Language (DML)
statements, and not in Data Definition Language (DDL) statements.
Assuming you can get past that problem, your use of mysqli is a little confused. You correctly bind your parameters and execute, but you've mixed up two different ways of getting at your results. Either
Use mysqli_stmt_get_result to fetch the result set and then use mysqli_fetch_assoc on that, or
Bind your results with mysqli_stmt_bind_result, and then use mysqli_stmt_fetch to fetch the next set of results into your bound variables. (Usually you'd iterate over the results using something like while(mysqli_stmt_fetch($stmt)){ //do stuff here }
Another way style, we can write it below:
$mysqli=new mysqli("host","user","pass","db");
$stmt = $mysqli->prepare($query);
$stmt->bind_param('s', $variable);
$stmt->execute();
$result = $stmt->get_result();
while($row = $result->fetch_assoc()){
....
}