I try to make a specific MySQL query work with PDO unnamed placeholders, and I suspect the problem might have something to do with the ' ' around the third question mark, but I just can't figure it out.
I get the exception:
'Invalid parameter number: number of bound variables does not match number of tokens'
Here's the relevant parts of the function, try-catch and the like removed for ease of reading. Function is called with $column and $mytype containing simple alphanumeric strings that worked fine with the earlier pure MySQL code, before I changed it to PDO-MySQL, so they should be ok.
define('SQL_TABLE', 'mytable');
function listThem($column, $mytype) {
# These lines succeed
$databaseHandle = new PDO('mysql:host=' . SQL_HOST . ';dbname=' . SQL_DATABASE, SQL_USER, SQL_PASSWORD);
$databaseHandle->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
# The following three lines cast the exception
$input = array(SQL_TABLE, $column, $mytype);
$statementHandle = $databaseHandle->prepare('SELECT *, ((100 * likes) / (dislikes + 1)) '
. "AS rating FROM ? WHERE ? REGEXP '?' ORDER BY rating DESC;");
$statementHandle->execute($input);
# . . . more code here
}
You can't bind table or field names as arguments using prepared statements. Parameter binding is only for values.
You will need to build those into the string. Just make sure you filter the values properly.
Also, you should not need to use '?', bound arguments take care of this.
Related
This question already has answers here:
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Closed 11 months ago.
I'm trying to create a select query with dynamic where clause and dynamic parameters but I always get error :
Warning: mysqli_stmt::bind_param(): Number of elements in type
definition string doesn't match number of bind variables
Which I sincerely do not understand since it seems the count is alright. So this is what the code really looks like in its rude format. I can't see what I'm doing wrong.
//get variables
$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';
//convert string to array
$socialArray = explode(',', $mediaArray)
//declare some variables to be used later
$andwhere = '';
$bp = '';
$socialmarray = ''
//get every value from array of social media
foreach($socialArray as $socialmedia){
$socialmarray .=$socialmedia.',';
$andwhere .= " AND socialmedianame=?";
$bp .='s';
}
//test strings
echo $wheres = $andwhere;//AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?
echo $bip = $bp.'s';//ssss
echo $validarayy = rtrim($socialmarray,',');//Facebook,Twitter,Twitch
//select query
$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");
$selectquery->bind_param("$bip",$otherMedia,$validarayy);
$selectquery->execute();
$resultquery = $selectquery->get_result();
Because:
You are using user-supplied data, you must assume that your query is vulnerable to a malicious injection attack and
the amount of data that is to be built into the query is variable/indefinite and
you are only writing conditional checks on a single table column
You should use a prepared statement and merge all of the WHERE clause logic into a single IN statement.
Building this dynamic prepared statement is more convoluted (in terms of syntax) than using pdo, but it doesn't mean that you need to abandon mysqli simply because of this task.
$mediaArray ='Facebook,Twitter,Twitch,';
$otherMedia = 'House';
$media = array_unique(explode(',', $mediaArray . $otherMedia));
$count = count($media);
$conn = new mysqli("localhost", "root", "", "myDB");
$sql = "SELECT * FROM mediaservices";
if ($count) {
$stmt = $conn->prepare("$sql WHERE socialmedianame IN (" . implode(',', array_fill(0, $count, '?')) . ")");
$stmt->bind_param(str_repeat('s', $count), ...$media);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $conn->query($sql);
}
foreach ($result as $row) {
// access values like $row['socialmedianame']
}
For anyone looking for similar dynamic querying techniques:
SELECT with dynamic number of LIKE conditions
INSERT dynamic number of rows with one execute() call
In your query:
$selectquery = $conn->prepare("select * from mediaservices where socialmedianame=? $wheres");
The ? represents one parameter to pass in, and the evaluation of $wheres adds another three, giving you four total parameters.
bind_param() should take a string representing the types of the variables to insert as the first parameter, and the variables themselves as the subsequent parameters.
In your bind:
$selectquery->bind_param("$bip",$otherMedia,$validarayy);
$bip evaluates to ssss and $otherMedia is a single string ("House"). You might expect $validarayy to be three strings, but rtrim() returns a string. Thus, it is only one string ("Facebook,Twitter,Twitch"). You pass through two variables when the query is expecting four:
$conn->prepare("select * from mediaservices where socialmedianame=House AND socialmedianame=Facebook,Twitter,Twitch AND socialmedianame=? AND socialmedianame=? AND socialmedianame=?"
To correct this, you'll want to convert $validarayy back to an array, and use the index for the various inputs:
$socialmarray2 = explode(',', $validarayy);
$selectquery->bind_param("$bip", $otherMedia, $socialmarray2[0], $socialmarray2[1], $socialmarray2[2]);
Also note that your sample code has a few missing semicolons; you'll need to fix these in order for your code to work correctly.
This can be seen working here.
Finally, note that even if you were to split the three strings out correctly, the selection of ... AND socialmedianame=Facebook AND socialmedianame=Twitter AND socialmedianame=Twitch will never match any results; socialmedianame can only contain one value. You're probably looking to substitute your AND statements with OR statements.
I'm having some difficulty with implementing fulltext() searching into my queries. Now the parameters in the AGAINST() segment won't invoke an error - unless they're wrapped in single-quotes.
Error: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
Which makes sense as they shouldn't be literals, instead, they should be strings, so the values aren't be bound, right? But in order for this query to function the parameters in AGAINST() must be surrounded by single quotes.
MATCH(features) AGAINST(':feature_set :feature_unset')
$bind_array[":feature_set"] = $feature_set;
$bind_array[":feature_unset"] = $feature_unset;
$stmt = $conn->prepare($query);
$stmt->execute($bind_array);
:feature_set :feature_unset
Would return a string formatted like so:
+Softaculous -Free Domain -Site Builder -Fantastico
Does anyone know of a solution for this? Much appreciated, thanks!
Try it this way
$query = '... MATCH(features) AGAINST(:against IN BOOLEAN MODE)';
$bind_array[":against"] = $feature_set . ' ' . $feature_unset;
$stmt = $conn->prepare($query);
$stmt->execute($bind_array);
Here is SQLFiddle demo.
i need help with my function thet i build , i trying to use MYSQLI prepare but i am not so good .
this is my function :
function insertToDb($table,$rowsarray,$valuequestionmarks,$lenstrings,$valarray){
$this->mysqli->set_charset("utf8");
if ($insert_stmt = $this->mysqli->prepare(
"INSERT INTO $table ($rowsarray)
VALUES
($valuequestionmarks)"
))
{
$insert_stmt->bind_param("$lenstrings",$valarray);
// Execute the prepared query.
if(!$insert_stmt->execute())
{
die("Execute failed: (" . $insert_stmt->errno . ") " . $insert_stmt->error);
}
}
}
And this is how i call :
$img = "something.jpg";
$uip = ulUtils::GetRemoteIP(false);
$table='forgotpassqm';
$rowsarray = 'email,text,img,ip';
$valuequestionmarks ='?,?,?,?';
$lenstrings ='ssss';
$valarray ='$email,$text,$img,$uip';
$func->insertToDb($table,$rowsarray,$valuequestionmarks,$lenstrings,$valarray);
And i keep get this error :
Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables
And the execute error :
Execute failed: (2031) No data supplied for parameters in prepared statement
i tryed allot of combination none work , i read other question none as my , and none worked or help either.
And i know this is about the ssss , but i using 4 and its seem to be alright so where i wrong here ?
Thanks allot.
EDIT :
$table output : forgotpassqm .
$rowsaray output: email,text,img,ip .
$valuequestionmarks output : ?,?,?,? .
$lenstrings output: ssss.
$valarray output: $email,$text,$img,$uip.
I think the problem is at $valarray.
Judging by the looks of it you are attempting to send a comma-delimited list of variables as an array (not how an array works) and you are using single quotes so variables aren't being interpolated to their values.
bind_param() expects a list of arguments after the type definitions. You aren't sending a list, you are sending the string '$email,$text,$img,$uip'.
Your call to that function should look like this:
$stmt->bind_param("ssss", $email, $text, $img, $uip);
I searched a lot but unable to find any question which is related to my problem so I posted this one.
I came to know that this following 3 line do the same work.
$q="insert into employee values('".$e."','".$nm."','".$desg."','".$sal."')";
$q="insert into employee values('$e','$nm','$desg','$sal')";
$q="insert into employee values('{$e}','{$nm}','{$desg}','{$sal}')";
$e, $name, &desg, &sal are variables.
I'm confused which one is best and why these 3 working same. 1st one is totally clear to me that it substitutes the variables with value and creates the query.
But in the 2nd and 3rd, its not clear to me how variables are substituted. That is from where I'm learning they says that if I insert a variable into a echo then it should be enclosed with {} or concatenated.
ex: echo "This is {$name}" / echo "This is ".$name;
So I'm confused.
There are not the different ways of writing queries, these are merely different ways to write strings in PHP. To clear any confusion, you should go through PHP strings manual and read about all possible ways to create strings. The documentation explains the four possible syntax plus how variables within strings are "parsed".
Before you write queries (safe ones) you must understand how strings work in PHP. You can then go through these answers to find out the proper way of writing queries.
As people have started to point out in the comments, none of these methods is advisable or safe.
The problem is SQL injection as explained here.
You want to use PDO. See this tutorial or this reference.
So to connect:
$dsn="mysql:host=127.0.0.1;dbname=myDatabase";
$username="myUsername";
$password="myPassword";
try {
$DBH=new PDO($dsn,$username,$password);
} catch(PDOException $e) {
echo $e->getMessage();
}
And a sample insertion:
$STH = $DBH->prepare("INSERT INTO job (snum, date, length, type, ref) VALUES (:u,:d,:l,:t,:r)");
$STH->bindParam(':u', $myVariable1);
$STH->bindParam(':d', $myVariable2);
$STH->bindParam(':l', $myVariable3);
$STH->bindParam(':t', $myVariable4);
$STH->bindParam(':r', $myVariable5);
try {
$STH->execute();
} catch(PDOException $e) {
echo $e->getMessage();
}
To answer your question:
This is simple string concatenation:
$q="insert into employee values('" . $e . "','" . $nm . "','" . $desg . "','" . $sal . "')";
This is value substitution, which PHP will do with string literals that use " ":
$q="insert into employee values('$e','$nm','$desg','$sal')";
The third sample is not correct. { and } are only necessary when you want to use the substitution from #2 with array values:
$q="insert into employee values('{$e[0]}','{$nm[0]}','{$desg['somekey']}','{$sal[o]}')";
As mentioned repeatedly, you seriously do not want to be using any of these to build a query string. Both PDO and the MySQLi library have parameterizing functions that are much safer.
On comparison of these four queries we got:
echo "My name is $name" //0.00099992752075195
echo "My name is ".$name; //0.00099992752075195
echo "My name is {$name}"; //0.0010001659393311
echo "My name is '{$name}'"; //0.0010001659393311
which proves first query will perform better.
these three are good in general use but are most vulnerable to sql injection better use:
use Prepared statements.
eg :
<?php
$q = 'Insert INTO counter(num) values (?);
$stmt = mysqli_prepare($dbc, 'i'); // check weather only integer is passed
mysqli_stmt_bind_param($stmt, 'i', $n);
for($n=1;$n<= 100; $n++)
{
mysqli_stmt_execute($stmt);
}
?>
`
Use PDO objects in php.
Is this code
class opinion
{
private $dbh;
var $opinionid,$opinion,$note,$actorid,$dateposted;
var $isnew=FALSE;
function loadby($column,$value,$operator="="){
$dbh = new PDO(I deleted parameters here);
$statement=$dbh->prepare("select * from fe_opinion where :column :operator :value");
$statement->bindParam(":column", $column);
$statement->bindParam(":value", $value);
$statement->bindParam(":operator", $operator); //UNSURE, DOUBTFUL
$statement->bindColumn("opinionid", $this->opinionid);
$statement->bindColumn("opinion", $this->opinion);
$statement->bindColumn("note", $this->note);
$statement->bindColumn("actorid", $this->actorid);
$statement->bindColumn("dateposted", $this->dateposted);
$statement->fetch();
return $statement->rowCount(); //please be 1
}
}
injection safe?
$statement->bindParam(":operator", $operator); //UNSURE, DOUBTFUL
Can I bind a parameter to a PDO statement as a comparison operator?
No, you cannot bind operators like that. As a workaround, you can dynamically create the "base" SQL query and use an operator whitelist (which is quite appropriate) to remain safe from injection:
function loadby($column,$value,$operator="="){
$dbh = new PDO(...);
$operator = getOperator($operator);
if(!$operator) {
// error handling
}
$statement=$dbh->prepare("select * from fe_opinion where :column $operator :value");
// the rest like you already do it
}
function getOperator($operator) {
$allowed_ops = array('=', '<', '>'); // etc
return in_array($operator, $allowed_ops) ? $operator : false;
}
Apart from this, the rest is fine and injection-proof "by definition".
Depending on the DBMS and PHP driver, prepared statements can be "real" or emulated.
In the first case, bind parameters are handled directly by the DBMS. In such case, handling an operator as parameter will probably trigger a syntax error. The SQL parser will analyse the query without even looking at parameters and will not find valid SQL code to work on.
In the second case, bind parameters are emulated by the driver: input values get inserted into SQL code (with adequate escaping) and the DBMS receives a complete regular query. I'm not really sure about how current drivers will behave (I'd need to test it) but even if they don't complain about invalid SQL, they'll hit a wall sooner or later: SQL operators are not strings.
Now, would it be a nice feature to get implemented some day? I doubt it is:
You won't benefit from pre-parsed SQL when running repetitive queries: if you change operators, you change the query.
You won't get safe SQL code. How could you?
As mentioned in comment I don't think it's possible to escape the operator and have it work as you expect. The resulting query would probably look something like:
'column' '=' 'value';
You don't need to escape the operator to avoid injection attacks, you can validate your operator before appending it to the string, consider:
class opinion
{
$validOperators = array('=', '>=', '>', '=<', '<');
function loadby($column,$value,$operator="=") {
// Validate operator
if (!in_array($operator, self::$validOperators)) {
throw new Exception('Invalid $operator ' . $operator . ')';
}
$statement=$dbh->prepare("select * from fe_opinion where :column " . $operator . " :value");
}
}
You can actually do it. The sql just gets more complicated. Depending on the number of combinations, the sql can get really huge. But, sometimes when there's only a few choices, its nice.
select *
from someTable
where (case :column
when 'age' then (case :operator
when '>' then age > :value
when '<' then age < :value
end)
when 'price' then (case :operator
when '>' then price > :value
when '<' then price < :value
end)
end)
and someOtherCol = 'foo'
The :value could be another column too, but you'll need to nest yet again another case construct like for the first column, and the combinations are really soaring now.
Anyway... just wanted to show it can be done.