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()){
....
}
Related
I have a conventional query that works just fine that looks like this:
$result = $mysqli->query("SELECT value FROM activities WHERE name = 'Drywall'");
This succeeds in returning a row. However, for the purposes of diagnosing the problem I'm having with a prepared statement, I tried an identical query as a prepared statement like so:
$stmt = $mysqli->prepare("SELECT value FROM activities WHERE name = 'Drywall'");
$stmt->execute();
Despite the fact these are identical query strings, $stmt->num_rows is always 0. Why would the conventional query work, but the prepared statement not when they are the same exact query? Also, I realize including 'Drywall' in the prepared query string runs counter to the purpose of prepared statements, but I was just trying to eliminate the possibility that bind_param() was the culprit. So I was using bind_param() to fill in placeholders and that wasn't working either, despite my double-checking at runtime that the variable I was binding contained the correct value.
I think you want to use
$stmt->store_result();
before the call
$stmt->num_rows();
see last line of the descripton in the manual for $stmt->num_rows() (http://www.php.net/manual/en/mysqli-stmt.num-rows.php).
Check for proper use of the mysqli->prepare. The function depends on a parameter to be passed. It is different from passing the values directly in the query but can use with another way.
Verify the manual:
http://www.php.net/manual/pt_BR/mysqli.prepare.php
Did you try something like this:
$stmt = $mysqli->prepare("SELECT value FROM activities WHERE name = 'Drywall'");
$stmt->execute();
$res = $stmt->get_result();
$row = $res->fetch_assoc();
PS:
Prepared statements are Good. I would urge you to ALWAYS consider using them.
But in this case, a simple query would be much more efficient (would incur fewer round trips) than a prepared statement.
I'll put here two examples where $stmt = $mysqli->prepare() + $stmt->bind_param() deny to work and I can't see for myself why.
Not working:
if ($stmt = $mySQLi->prepare("DROP DATABASE ?")) {
$stmt->bind_param('s', $db_name);
$stmt->execute();
$stmt->store_result();
}
Current working workaround:
if ($stmt = $mySQLi->prepare("DROP DATABASE $db_name")) {
//$stmt->bind_param('s', $db_name);
$stmt->execute();
$stmt->store_result();
}
Not working:
if ($stmt = $strSQLi->prepare("SELECT ? FROM Strings.texts WHERE keyName = ? LIMIT 1")) {
$stmt->bind_param('ss', strtolower($lang), strtolower("help_".$key));
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($result);
}
Current working workaround:
if ($stmt = $strSQLi->prepare("SELECT {strtolower($lang)} FROM EVEStrings.texts WHERE keyName = ? LIMIT 1")) {
$stmt->bind_param('s', strtolower("help_".$key));
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($result);
}
Any idea WHY?
This is how mysqli::prepare() is working. It's completely written in the documentation.
http://php.net/manual/en/mysqli.prepare.php
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. It's not
allowed to compare marker with NULL by ? IS NULL too. In general,
parameters are legal only in Data Manipulation Language (DML)
statements, and not in Data Definition Language (DDL) statements.
This part mostly: they are not allowed for identifiers (such as table or column names)
The idea of binding parameters it that you are sending to the database engine the query and in the runtine it binds the values you have given. If the table is not specified, the engine cannot build a valid statement, thus to continue executing the query and bind params is impossible.
I would not suggest to use dynamic table names in general, whether it is safe or not. However, if you really insist to do it, do not let the user to decide that. Decide the {strtolower($lang)} on application level (i.e. from an array), not from the user input.
As far as I know from PDO (and I think it's the same for mysqli) this can't be done.
bind_param and bind_value() can only bind values, not table or column names.
You have to filter your data manually, maybe with a whitelist method.
I want to make a "dynamic" WHERE clause in my query based on a array of strings. And I want to run the created query using Mysqi's prepared statements.
My code so far, PHP:
$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
$searchStr .= "OR tags.tag LIKE ? ";
}
My query:
SELECT tag FROM tags WHERE $searchStr;
More PHP:
$stmt -> bind_param(str_repeat('s', count($searchArray)));
Now this obviously gives me an error since the bind_param part only contains half the details it need.
How should I proceed?
Are there any other (better) way of doing this?
Is it secure?
Regarding the security part of the question, prepared statements with placeholders are as secure as the validation mechanism involved in filling these placeholders with values up. In the case of mysqli prepared statements, the documentation says:
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. It's not allowed to compare marker with NULL by ? IS NULL too. In general, parameters are legal only in Data Manipulation Language (DML) statements, and not in Data Definition Language (DDL) statements.
This clearly excludes any possibility of modifying the general semantic of the query, which makes it much harder (but not impossible) to divert it from its original intent.
Regarding the dynamic part of your query, you could use str_repeat in the query condition building part, instead of doing a loop:
$searchStr = 'WHERE tags.tag LIKE ?' .
str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');
For the bind_param call, you should use call_user_func_array like so:
$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);
Hopefully the above snippet should bind every value of the $bindArray with its corresponding placeholder in the query.
Addenum:
However, you should be wary of two things:
call_user_func_array expects an integer indexed array for its second parameter. I am not sure how it would behave with a dictionary.
mysqli_stmt_bind_param requires its parameters to be passed by reference.
For the first point, you only need to make sure that $bindArray uses integer indices, which is the case in the code above (or alternatively check that call_user_func_array doesn't choke on the array you're providing it).
For the second point, it will only be a problem if you intend to modify the data within $bindArray after calling bind_param (ie. through the call_user_func_array function), and before executing the query.
If you wish to do so - for instance by running the same query several times with different parameters' values in the same script, then you will have to use the same array ( $bindArray) for the following query execution, and update the array entries using the same keys. Copying another array over won't work, unless done by hand:
foreach($bindArray as $k => $v)
$bindArray[$k] = some_new_value();
or
foreach($bindArray as &$v)
$v = some_new_value();
The above would work because it would not break the references on the array entries that bind_param bound with the statement when it was called earlier. Likewise, the following should work because it does not change the references which have been set earlier up.
array_walk($bindArray, function($k,&$v){$v = some_new_value();});
A prepared statement needs to have a well-defined number of arguments; it can't have any element of dynamic functionality. That means you'll have to generate the specific statement that you need and prepare just that.
What you can do – in case your code actually gets called multiple times during the existence of the database connection - is make cache of those prepared statements, and index them by the number of arguments that you're taking. This would mean that the second time you call the function with three arguments, you already have the statement done. But as prepared statements don't survive the disconnect anyway, this really only makes sense if you do multiple queries in the same script run. (I'm deliberately leaving out persistent connections, because that opens up an entirely different can of worms.)
By the way, I'm not an MySQL expert, but would it not make a difference to not have the where conditions joined,but rather writing WHERE tags in (tag1, tag2, tag3, tag4)?
Solved it by the help of an answer found here.
$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)
$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);
for ($i = 0; $i < count($searchArray); $i++){
$bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
$$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
}
call_user_func_array(array($stmt, 'bind_param'), &$bind_names);
$stmt -> execute();
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()
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.