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.
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/
Alright so this bugs the crap out of me, and I can't seem to find anything in the PHP documentation, nor anywhere in the Google resultosphere, so maybe someone can help here.
I'm trying to make a simple request to a database to return a varchar(30).
Code:
$qryID = "select idPhotos from housesphotos where idHouse = ?";
//Prepare the query
$stmt = mysqli_prepare($link, $qryID);
$x = 106;
//Bind the login parameter to the statement
if(mysqli_stmt_bind_param($stmt, "i", $x)){
if(mysqli_stmt_execute($stmt)){
//Bind every column in the select
if(mysqli_stmt_bind_result($stmt, $photoID)){
// $photoID = mysqli_stmt_fetch($stmt);
echo $photoID;
}
}
}
if(empty($photoID)){
printf("EMPTY");
}
As you can guess the output was EMPTY. I have tried using this solution: Strange issue with mysqli_stmt_bind_result but it did not work.
To make sure that everthing was suppost to go correctly I have made the query in the mysql and it worked wonders:
select idPhotos from housesphotos where idHouse = 106
OUTPUT:
591219e92b2fe_591219e92b302
The housephotos is a varchar(30) field. I'm not sure if it's the field type that is messing with the return value. I'm not sure also if it's the connections that I made earlier on the code but I have tried unset($stmt) and other variables to solve the problem but it's not working.
Is this some kind of bug with mysqli_stmt_bind_result() or just a new person's mistake?
You're not actually fetching the results - you commented the mysqli_stmt_fetch() out. The fetch also returns a boolean, not the actual results. Uncomment that line, and remove the variable assignment to it.
$qryID = "select idPhotos from housesphotos where idHouse = ?";
//Prepare the query
$stmt = mysqli_prepare($link, $qryID);
$x = 106;
//Bind the login parameter to the statement
if(mysqli_stmt_bind_param($stmt, "i", $x)){
if(mysqli_stmt_execute($stmt)){
//Bind every column in the select
if(mysqli_stmt_bind_result($stmt, $photoID)){
mysqli_stmt_fetch($stmt); // Remove the comment and variable assignment
echo $photoID;
}
}
}
If you still don't get any results, it might be because the number of rows returned was zero (you can check that with $stmt->num_rows after executing), but since you said the query worked in phpMyAdmin, it's likely that there's some sort of error you're ignoring. To check for errors, add an else to your if conditions, and log $stmt->error inside.
http://php.net/manual/en/mysqli-stmt.fetch.php
It seems that PHP 5.4 (at least the way it's installed on Comcast servers) has a bug that causes bind-result to fail on a varchar field. Changing the field definition to "text" solves the problem.
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);
I am trying to run the following query in php, but its failing.
$results = $conn->prepare("INSERT INTO book_loans
VALUES (:book_id, :branch_id, :card_no, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 14 DAY))");
//Binding params and executing insert for checking out
$results->bindParam(':book_id', $book_id, PDO::PARAM_STR);
$results->bindParam(':branch_id', $branch_id, PDO::PARAM_INT);
$results->bindParam(':card_no', $card_no, PDO::PARAM_STR);
if($results->execute()) {
return array("success");
} else {
return array("failure");
}
I am returning arrays with strings to check back and display a message.
But even for valid elements, the query is failing.
What is the error message?
Usually the cause is an error in the SQL query! Check that the query actually works in the MySQL console.
If you require more specific help you need post the desription of the book_loans table. Can you post the create table statement for this table?
In stead of returning array ('failure') return array($results->errorInfo()), analyze the error text and then fix it
My code currently uses mysqli::query and checks mysqli::$warning_count. When trying to insert text into an integer column, 0 gets inserted and a warning is generated. However, I'm just starting to learn prepared statements and for the same query, no warning is generated.
Here's an excerpt from my code:
$db_err_msg = 'Database Error: Failed to update profile';
$sql = "UPDATE tblProfiles SET intBookId = ? WHERE lngProfileId = ?";
$stmt = $mysqli->prepare($sql) or output_error($db_err_msg);
$book = 'CA';
$id = 10773;
$stmt->bind_param('ii', $book, $id) or output_error($db_err_msg);
$stmt->execute() or output_error($db_err_msg);
echo '$stmt->affected_rows is ', $stmt->affected_rows, "\n";
if ($mysqli->warning_count) {
$warnings = $stmt->get_warnings();
do {
trigger_error('Database Warning (' . $warnings->errno . '): '
. $warnings->message, E_USER_WARNING);
} while ( $warnings->next() );
}
else {
echo 'no warnings', "\n";
}
which produces the following output:
$stmt->affected_rows is 1
no warnings
Note that the intBookId column has a TINYINT data type. The same query generates a warning when using mysqli::query, but not when using prepared statements.
Enabling strict mode does not help. It will turn the warning into an error when using mysqli::query, but when using prepared statements the column is silently updated with a 0;
For the record, my application already does extensive validation before it gets to this point. But I wanted this extra validation as way to catch anything I might miss by mistake.
Note: It's beyond the scope of the current project to switch to PDO.
Why is this happening with MySQLi prepared statements? Is this expected behaviour?
Though it may not be immediately obvious, it is expected behaviour.
Because of this line of code:
$stmt->bind_param('ii', $book, $id) or output_error($db_err_msg);
PHP is casting the value to an integer before sending it to MySQL. MySQL receives the value 0 which is a valid value for the column and no warning is generated.
If you don't want PHP to do this, then you need to send it as a string. Like so:
$stmt->bind_param('si', $book, $id) or output_error($db_err_msg);
MySQL will receive the string 'CA' and generate a warning about it being an incorrect integer value.
Note: By sending everything as a string, MySQL will have to do a little more processing to convert the strings to integers, but it was already doing that anyway when the whole query was sent as a string using mysqli::query.
Note: A related problem occurs when using integers that are greater than PHP_INT_MAX. If you think an integer value will surpass that maximum (which is platform-dependent and only 2147483647 on 32-bit platforms) and precision is important, it's safer to send it as a string.