I have a method verifyCredentials in a class that's used to verify user credentials. I'm rewriting it to make use of PHP's PDO instead of DBMS dependant mysqli-statements. I'm having trouble with getting parameters bound to my prepared query.
PDO always throws the warning
Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in [file] on line [line]
I'm clearly missing something here, but I can't figure out what for the life of me.
Code snippet, everything in caps except DBH and STH are defined by an external constants.php file:
class FancyClass{
function __construct(){
try{
$this->DBH=
new PDO(PDO_DRIVER.':host='.DB_HOST.';dbname='.DB_DB,
DB_USER, DB_PWD);
}
catch(PDOException $e){
return $e->getMessage();
}
$this->queryGetPwdForUser="select :userIdCol , :pwdCol from :usersTable where :aliasCol = ':alias' limit 1"
}
function __destruct(){
$this->DBH=null;
}
function verifyCredentials($alias,$pwd){
$STH=$this->DBH->prepare($this->queryGetPwdForUser);
$STH->bindParam(':userIdCol',$userIdCol);
$STH->bindParam(':pwdCol',$pwdCol);
$STH->bindParam(':usersTable',$usersTable);
$STH->bindParam(':aliasCol',$aliasCol);
$STH->bindParam(':alias',$alias);
$userIdCol=DB_COLUMN_USERID;
$pwdCol=DB_COLUMN_USERPWD;
$usersTable=DB_TABLE_USERS;
$aliasCol=DB_COLUMN_USERALIAS;
$STH->execute();
$result=$STH->fetch();
if($result==false) return false;
$hasher = new PasswordHash(50,false);
if($hasher->CheckPassword($pwd,$result[DB_COLUMN_USERPWD]))
return $result[DB_COLUMN_USERID];
else
return false;
}
}
To clarify what needs to be done, even though fixing the ':alias' quotes suggested before, MySQL keywords (like SELECT, INSERT), table names and column names can't be bound through placeholders using prepared statements. In order to dynamically create a MySQL query, you have to replace these values in some other way.
I noticed you're predefining the SQL queries already, so using something like str_replace or maybe define a general method which may replace these placeholder with values like so:
$sql = str_replace(array(
':userIdCol',
':pwdCol',
':usersTable',
':aliasCol'
), array(
$userIdCol,
$pwdCol,
$usersTable,
$aliasCol
), $this->queryGetPwdForUser);
Obviously the approach for the prepared statement in this case
$STH=$this->DBH->prepare($sql);
$STH->bindParam(':alias',$alias);
Related
I'm working on a script that is essentially loading data from an API into a local MySQL database. The values are variable depending on what is returned by the API.
So far everything is working just fine up until I try to actually insert the rows into the MySQL db. Specifically, I know I should be using prepared statements, but I'm having trouble when I try to bind the variables to the prepared statement. When I try to run the below code, I get:
PHP Warning: mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables in /opt/awn2sql/functions.php on line 212
Here's the code in question:
$readingValues = array_values($read); //array of just the values in the original array
array_push($readingValues, $devicemac); //add the MAC address of the device that recorded the reading to the array
$columns = implode(", ",$readingTypes); //create a string of column names to build the SQL query
$valuesCount = count($readingValues); //get a count of the values to fill an array with placeholders
$stmt_placeholders = implode(',',array_fill(0,$valuesCount,'?')); //fill an array with placeholders (i.e. ?,?,?) - see above
$stmt_param = null; //$stmt_param will hold the type definitions string for binding the
foreach ($readingValues as $param) { //iterate through each value in the $readingValues array, get the type, and add it to the type definitions string
if (gettype($param) == 'integer')
{
$stmt_param = $stmt_param.'i';
}
else if (gettype($param) == 'double')
{
$stmt_param = $stmt_param.'d';
}
else if (gettype($param) == 'string')
{
$stmt_param = $stmt_param.'s';
}
else if (gettype($param) == 'blob')
{
$stmt_param = $stmt_param.'b';
}
else
{
echo "Invalid data type!";
}
}
$val_insert_query = "INSERT INTO ".$config['mysql_db'].".readings (".$columns.") VALUES (".$stmt_placeholders.");"; //Template for the query
$stmt=$mysqli->prepare($val_insert_query); //Prepares the template for the query for binding, prepared statement becomes $stmt
echo ($stmt_param." (".strlen($stmt_param).")\n"); //for debugging, echo the type definiton string and get its length (which should match the number of values)
echo (count($readingValues)); //count the number of values, which should match the number of elements in the type defintion string
$stmt->bind_param($stmt_param, $readingValues); //Binding
$stmt->execute(); //execute the statement
I freely admit that I'm a bit of a newbie at this, so I'm open to any and all suggestions on how to do this better. For what it's worth, there's never any direct user input, so I'm relatively unconcerned about security concerns if that makes a difference in how best to approach this.
Thanks in advance!
bind_param() actually takes variable arguments, not an array argument. But modern PHP has syntax for turning an array into multiple scalar arguments:
$stmt->bind_param($stmt_param, ...$readingValues); //Binding
This is equivalent to passing the array elements as individual arguments:
$stmt->bind_param($stmt_param, $readingValues[0], $readingValues[1],
$readingValues[2], etc.);
But that's awkward if you don't know how many elements are in the array.
FYI, I like to use PDO instead of mysqli. You don't have to bind anything, just pass the array of values as the argument to execute():
$stmt=$pdo->prepare($val_insert_query);
$stmt->execute( $readingValues );
I find PDO to be a lot easier. The reason to use mysqli is if you have a lot of legacy code from the mid-2000's that you need to adapt. If you're just starting out, you have no old code. So you might as well adopt PDO to start with.
There's a good tutorial for PDO: https://phpdelusions.net/pdo/
I know there are many questions similar to my question. But I really can not figure the problem here. I have class named 'UsersClass' which is responsible on every task related to users. And I use mysqli prepare statement to insert or select data from the database, the problem is that in many cases prepare statement return false. I solved the issue by making new connection every while, but this caused another issue in other functions "2006: Mysql server has gone away" See code below please:
Here and in other functions the prepare return false.
function isPostingAllowed() {
$this->setPsSelectUsers($this->_connection->prepare("Select * from userpermissions where userpermissions.UserId = ? and userpermissions.PermissionId = 1"));
$this->_psSelectUsers->bind_param('i',$this->UserId);
$this->_psSelectUsers->execute();
if ( false===$this->_psSelectUsers ) {
die('prepare() failed: ' . htmlspecialchars($this->_connection->error));}
if ($this->_psSelectUsers->fetch()){
return true;
}else{
return false;
}}
function setPsSelectUsers($stmt) {
$this->unsetPsSelectUsers();
// mysqli_close($this->_Connection);
// $this-> __construct();
$this->_psSelectUsers= $stmt;}
When I uncomment these two lines The first function will work and prepare staement will not return false, but in this case the following function will throw error 2006:
function checkUserAuthentication() {
$this->setPsSelectUsers($this->_connection->prepare("SELECT UserLogInName FROM Users WHERE UserLogInName=? AND UserPassword=?"));
$this->_psSelectUsers->bind_param('ss',$this->UserLogInName, $this->UserPassword);
$this->_psSelectUsers->execute();
if ($this->_psSelectUsers->fetch()){
return true;
}else{
return false;
}}
So How to solve the first problem without making new problem?
Problem:
... I use mysqli prepare statement to insert or select data from the database, the problem is that in many cases prepare statement return false.
That's because you're running the queries in out of order fashion i.e. you're executing ->prepare() before closing the previous statement object. Given your current code, add the following error reporting code in your prepared statements,
if(!$this->_connection->prepare(...)){
printf('errno: %d, error: %s', $this->_connection->errno, $this->_connection->error);
die();
}
If you look at $this->_connection->error, you'll see the following error,
Commands out of sync; you can't run this command now
This issue has been documented in many forums, such as:
From the MySQL documentation,
If you get Commands out of sync; you can't run this command now in your client code, you are calling client functions in the wrong order.
From this SO thread,
You can't have two simultaneous queries because mysqli uses unbuffered queries by default (for prepared statements;...
Solution:
Execute the commands in correct order, (See this example code)
Open up the connection (Once: at the very beginning, not during the execution of every query)
Create a prepared statement
Bind parameters
Execute the query
Bind result variables
Fetch value into those bound variables
Close the statement object
Close the connection (Once: at the very end, not during the execution of every query)
(Follow steps 2 to 7 for executing all of your queries, though one or more steps might be optional based on of your query)
So the solution is, close the previous statement object before calling ->prepare() again. Take this method call $this->unsetPsSelectUsers(); out of the setPsSelectUsers() method and place it before the if ($this->_psSelectUsers->fetch()){...}else{...} block of isPostingAllowed() and checkUserAuthentication() methods. Furthermore, save the status of $this->_psSelectUsers->fetch() method call in a variable and use it in the subsequent if block. So your code should be like this:
public function isPostingAllowed() {
$this->setPsSelectUsers($this->_connection->prepare("Select * from userpermissions where userpermissions.UserId = ? and userpermissions.PermissionId = 1"));
if(!$this->_psSelectUsers){
printf('errno: %d, error: %s', $this->_connection->errno, $this->_connection->error);
die();
}
$this->_psSelectUsers->bind_param('i',$this->UserId);
$this->_psSelectUsers->execute();
$status = $this->_psSelectUsers->fetch();
$this->unsetPsSelectUsers();
if ($status){
return true;
}else{
return false;
}
}
private function setPsSelectUsers($stmt){
$this->_psSelectUsers= $stmt;
}
private function unsetPsSelectUsers() {
if (isset($this->_psSelectUsers)) {
$this->_psSelectUsers->close();
unset($this->_psSelectUsers);
}
}
public function checkUserAuthentication() {
$this->setPsSelectUsers($this->_connection->prepare("SELECT UserLogInName FROM Users WHERE UserLogInName=? AND UserPassword=?"));
if(!$this->_psSelectUsers){
printf('errno: %d, error: %s', $this->_connection->errno, $this->_connection->error);
die();
}
$this->_psSelectUsers->bind_param('ss',$this->UserLogInName, $this->UserPassword);
$this->_psSelectUsers->execute();
$status = $this->_psSelectUsers->fetch();
$this->unsetPsSelectUsers();
if ($status){
return true;
}else{
return false;
}
}
Moreover, you don't have to use this setPsSelectUsers() method anymore, you can directly use the property $_psSelectUsers in your methods like this:
$this->_psSelectUsers = $this->_connection->prepare(...);
I have spent a lot of time trying to figure out what is wrong here and I am stumped.
Here's the specific PHP code that is failing:
//Handy function I use to do all my bound queries - yes you can have it.
function prepareBindAndExecute($pdo, $qry, $aParams) {
if (!$stmt = $pdo->prepare($qry)) {
setSessionError("Failed to prepare $qry");
return false;
}
foreach ($aParams as $aParam) {
// $aParam[0] = ":labelToBind"
// $aParam[1] = value to Bind
// $aParam[2] = PDO::PARAM_TYPE
if (strpos($qry, $aParam[0]) !== false) { // skip binding if label isn't in query. This allows built up queries to not fail if parts were not created for a parameter.
if (!$stmt->bindParam($aParam[0], $aParam[1], $aParam[2])) {
setSessionError("Failed to bind $aParam[1] as $aParam[0] to $qry Error Info:".print_r($stmt->errorInfo()));
return false;
}
}
}
if (!$stmt->execute()) {
setSessionError("Failed to execute $qry bound with ".json_encode($aParams).' Error Info:'.print_r($stmt->errorInfo()));
return false;
}
return $stmt;
}
// Here's the problem call: The member_login is a VARCHAR(32) receiving an email address string
// and the submission_date is a DateTime column receiving the current date.
$stmt = prepareBindAndExecute($pdoRW,
'INSERT INTO videosubmissions (member_login, submission_date) VALUES (:login, :submission-date)',
[ [ ':login', $info['login'], PDO::PARAM_STR ],
[ ':submission-date', $submission_date->format(DateTime::ISO8601), PDO::PARAM_STR ] ]);
Here's the results I get with this code:
Failed to execute
INSERT INTO videosubmissions (member_login, submission_date) VALUES
(:login, :submission-date) bound with
[[":login","xTst2#gmail.com",2],
[":submission-date","2014-02-15T20:37:01+0100",2]]
With a related PHP error in the error log of:
PHP Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter
number: parameter was not defined in...
This simple case is NOT a mismatched number of parameters case as there are only two labels to bind. My helper function has been working with much more complex queries than this one.
For awhile I thought I had this fixed by quoting the :label tag -> VALUES (":label", :submission_date...
This let the call succeed but resulted in the sting ":label" being inserted into the DB and would in fact, by my understanding, cause a true parameter count mismatch.
The PDO::PARAM_ constants do not offer a DATE or DATETIME flavor. (see http://www.php.net/manual/en/pdo.constants.php)
I have verified that my helper function did not skip binding any parameters - and we can seed this from the error message returned.
I have also tried binding the submission_date with a DateTime PHP object instead of a string and I have tried various data/time formated strings.
I am wondering if the # in the login parameter is somehow screwing up the binding.
It would be nice if PDO would offer up what the actual query is that is being sent to the mySql but this might be buried in the driver.
Hopefully I am just missing something dumb.
Thanks!!!
Dunno what's the problem with this particular code, but if to take out all the useless parts it can be boiled down to this, and I am pretty sure would work
function query($pdo, $qry, $aParams) {
$stmt = $pdo->prepare($qry);
$stmt->execute($aParams);
return $stmt;
}
$sql = 'INSERT INTO videosubmissions (member_login, submission_date) VALUES (?, ?)';
query($pdoRW, $sql, [$info['login'], $submission_date->format(DateTime::ISO8601)]);
You can't have - in a placeholder name:
INSERT INTO [...snip...] (:login, :submission-date)',
^---
The - is not a valid char in a placeholder name, and MySQL will interpreter this as
... :submission MINUS date
Since you're not binding a value for submission, you get the invalid parameter number error. Even if you by-chance did have another placeholder whose name WAS :submission, you'd still end up with an SQL parser error due to the undefined/non-existent date field being used in what MySQL is seing as a subtraction operation.
So i have a function thats supposed to handle all data execute operations: sql
function loadResult($sql)
{
$this->connect();
$sth = mysql_query($sql);
$rows = array();
while($r = mysql_fetch_object($sth)) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
I want to convert it to pdo and this is what i have so far: pdo
function loadResult($sql)
{
$this->connect();
$sth = $this->con->prepare($sql);
//execute bind values here
$sth->execute();
$rows = array();
while ( $r = $sth->fetch(PDO::FETCH_OBJ) ) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
Here is an example of a function on how am using it to view data from the database:
function viewtodolist()
{
$db=$this->getDbo(); //connect to database
$sql="SELECT * FROM mcms_todolist_tasks";
//maybe the bind values are pushed into an array and sent to the function below together with the sql statement
$rows=$db->loadResult($sql);
foreach($rows as $row){echo $row->title; //echo some data here }
}
I have just pulled out the important snippets so some variables and methods are from other php classes. Somehow, the mysql query works fine, but the PDO query is giving me headaches on how to include bindValue paremeters most probably in the viewtodolist() function to make it reusable. Any suggestions/recommendations are welcome.
Since your existing function accepts a fully-formed SQL string, with no placeholders, you don't need to use prepare + bind. Your code as written should work fine, or you could use PDO::query() to execute the SQL in one step.
If you want to use parameterised queries, then your loadResult function is going to have to change a bit, as is the way you write your SQL. The example SQL you give doesn't actually have anything in that could be turned into a parameter (column names and table names can't be parameters as discussed here), but I'll use an imaginary variation:
// Get the todo tasks for a particular user; the actual user ID is a parameter of the SQL
$sql = "SELECT * FROM mcms_todolist_tasks WHERE user_id = :current_user_id";
// Execute that SQL, with the :current_user_id parameter pulled from user input
$rows = $db->loadResult($sql, array(':current_user_id' => $_GET['user']));
This is a nice secure way of putting the user input into the query, as MySQL knows which parts are parameters and which are part of the SQL itself, and the SQL part has no variables that anyone can interfere with.
The simplest way of making this work with your existing loadResult function would be something like this:
// Function now takes an optional second argument
// if not passed, it will default to an empty array, so existing code won't cause errors
function loadResult($sql, $params=array())
{
$this->connect();
$sth = $this->con->prepare($sql);
// pass the parameters straight to the execute call
$sth->execute($params);
// rest of function remains the same...
There are cleverer things you can do with parameterised queries - e.g. binding variables to output parameters, preparing a query once and executing it multiple times with different parameters - but those will require more changes to the way your calling code works.
New to this new and secure way of handling SQL's in PHP and MySql driven web based application, to secure the code from SQL injections. I am planning to start using mysqli with PDO. Can anyone please outline how should i get started and proceed.
Any reference to any article will also be helpful.
Thanks in advance.
To create the connection
try {
$db = new PDO("mysql:dbname=".DB_NAME.";host=".DB_HOST,DB_USER,DB_PWD);
} catch (PDOException $e) {
die("Database Connection Failed: " . $e->getMessage());
}
Then to prepare a statement
$prep = $db->prepare("SELECT * FROM `users` WHERE userid = ':id'");
As you can see, you label each parameter you'd like by prefixing any string with ':'. Then all you do is pass an array mapping the parameter (:id) to the value when you execute.
if (!$prep->execute(array(":id" => $userinput))) {
$error = $prep->errorInfo();
echo "Error: {$error[2]}"; // element 2 has the string text of the error
} else {
while ($row = $prep->fetch(PDO::FETCH_ASSOC)) { // check the documentation for the other options here
// do stuff, $row is an associative array, the keys are the field names
}
}
Instead of PDO::FETCH_ASSOC with the "fetch" function, there are various other ways to get your data. You can use fetchAll to get an array of ALL the results at once instead of just going row by row. Or you can get the array of information as a 0-indexed array, or you can even fetch the results directly into a class instance (if the field names line up with the properties of the class.)
All the documentation of PDO can be found here: PHP.net PDO Manual