PDO - Dynamic bindParam - value of 0 strange result - php

So thanks to suggestions from users here I have started porting my code over to PDO. Everything was going well until one little issue.
I have a small function to handle my database calls, which basically generates the SQL query, does the $dbh->prepare ($sql), then loops through and binds the values, then executes the query.
$sth = $dbh->prepare ($sql);
// bind parameters
if ($action == 'insert' || $action == 'update') {
reset ($array);
foreach ($array as $key => &$value) {
if ($value != 'NOW()') {
$sth->bindParam (':' . $key, $value);
}
}
}
$sth->execute();
This works fine until I need to insert a value of '0'. No errors are returned, but the value that is inserted into the db turns out to be the maximum value for the column type in the table, in this case is "137".
I'd prefer if someone could explain what is happening as well as providing a solution rather than just give me a fix so I can better understand this.
Cheers,
Luke

You're not binding the parameters correctly, check out the manual.
You should use:
$sth->bindParam (':' . $value, [PARAM TYPE - example: PDO::PARAM_INT]);

Related

PHP function to use prepared SQL statements - using implode to enter values of array separated by commas as arguments for function

I have recently asked some questions about security against SQL injection vulnerabilities. I decided to make a function that would do a sql query using a prepared statement so I didn't have to write out so many lines of code for every query:
function secure_sql($query, $values, $datatypes){
global $link;
$stmt = mysqli_prepare($link, $query);
foreach($values as &$value){
$value = mysqli_real_escape_string($link, $value);
mysqli_stmt_bind_param($stmt, substr($datatypes, 0), $value);
$datatypes = substr($datatypes, -(strlen($datatypes)-1));
}
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, implode(", ", $values));
$results = mysqli_stmt_get_result($stmt);
return $results;
}
where query is the prepared query (with ?s), $values is an array of all the variables replacing the placeholder ?s (in order) and $datatypes is a string containing all the data types for the variables. $link is a database connection.
So I have two main questions.
1) It is not working. I think this must be because of implode maybe not being used correctly in this context. What would I use instead? I can't use call_user_func_array because I also need to have $stmt as an argument. I have tried using array_unshift to add $stmt to the beginning of the argument, but it doesn't work.
2) If I do get it to work, what could be done to improve it? I am still a PHP and SQL beginner.
EDIT: I have solved the problem now. Thank you all for your helpful comments and answers!
Instead of directly giving you the code I came up with, I feel that explaining it is necessary, seeing how some of your ways of thinking are rather incorrect.
Firstly, the use of mysqli_stmt_bind_param() confuses me - your second argument expression (substr($datatypes, 0)) returns a value of all the datatypes, yet you are only binding one. What I think you meant to put is:
mysqli_stmt_bind_param($stmt, $datatypes[0] /* <-- retrieves the first character */, $value);
But more importantly, you should only call mysqli_stmt_bind_param() once, which gives you some bigger difficulties... To omit the foreach-loop, how about call_user_func_array()? (Actually, it's very possible to keep $stmt as an argument):
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $values));
//calls mysqli_stmt_bind_param($stmt, $datatypes, $values[0], $values[1]... values[n]);
If you're confused by the thing above, look at it in this way:
call_user_func_array //a call to mysqli_stmt_bind_param, with all the appropriate parameters
(
"mysqli_stmt_bind_param", //the function to call
array_merge //an array consisting of the statement, the datatype and all the values
(
array($stmt, $datatypes), //statement and datatype-parameters
$values //all the values
)
);
This does, however, require your $values-array to consist of references, as the mysqli_stmt_bind_param expects your values to be so. If you still want to pass them as values into your function, you could add this, and later pass $ref_values into the call_user_func_array() function:
foreach ($values as &$value) $ref_values[] = &$value;
Now we come to the use of implode(), which also is incorrect. To reference from the PHP-manual:
Implode (Return Value):
Returns a string containing a string representation of all the array elements in the same order, with the glue string between each element.
mysqli_stmt_bind_result (Second-Nth Parameter):
The variable to be bound.
So what you're attempting to do here is to make a string returned by implode() a variable, which makes no sense. Although luckily, mysqli_stmt_get_result() returns an object which is fetched after execution, meaning that the bind_result-function isn't needed. So try removing that line.
In all, I would re-write the code like this:
function secure_sql($query, $values, $datatypes) {
global $link;
$stmt = mysqli_prepare($link, $query);
foreach ($values as &$value) $ref_values[] = &$value;
call_user_func_array("mysqli_stmt_bind_param", array_merge(array($stmt, $datatypes), $ref_values));
mysqli_stmt_execute($stmt);
return mysqli_stmt_get_result($stmt);
}
To me, it sounds like that should work, but if it doesn't, tell me (I might be missing something or thinking incorrectly somewhere).
To answer the second question, it all looks good, except that I wouldn't advice you to use mysqli_-functions, as they will get removed eventually (as said in the PHP-manual). If you're planning to use objects instead, most of it is similar when it comes to my changes (apart from the fact that you need to use object-properties and object-methods with them instead...), except the call_user_func_array() function. Luckily though, calling a method with it is possible as well, by specifying an array as the first parameter, consisting of the object and the method name (call_user_func_array($prepared_obj, "bind_param") ...)).
Edit: Considering how necessary it is, I made a function that does the same thing, but works on an mysqli object instead:
function secure_sql($query, $values, $datatypes) {
global $mysqli_object; //declared with $mysql_object = new mysqli(...)
$stmt = $mysqli_object->prepare($query);
foreach ($values as &$value) $ref_values[] = &$value;
call_user_func_array(array($stmt, "bind_param"), array_merge(array($datatypes), $ref_values));
$stmt->execute();
return $stmt->get_result();
}

PDO: bindParam empty string

Theres's a similar question here, but actually that doesn't give me the answer:
PHP + PDO: Bind null if param is empty
I need my statement work in a loop, with only changing the binded variables.
Like:
$this->array = array(
"cell1" => "",
"cell2" => "",
);
$this->sth = $db->prepare("INSERT INTO `table`
(`coloumn1`, `coloumn2`)
VALUES (:coloumn1, :coloumn2)");
$this->sth->bindParam(:coloumn1, $this->array['cell1'], PDO::PARAM_STR);
$this->sth->bindParam(:coloumn2, $this->array['cell2'], PDO::PARAM_STR);
//Data proccessing...
foreach($data as $value){
$this->array['cell1'] = $value['cell1'];
$this->array['cell2'] = $value['cell2'];
try {
this->sth->execute();
print_r($this->sth->errorInfo());
}
catch(PDOException $e){
echo 'sh*t!';
}
}
Everything works well until either of the values is an empty string.
My problem is when 'cell1' is an empty string, the bound parameter is a nullreference, and it won't work. But I need the referenced binding because of the loop, so bindValue isn't a solution.
And I need the loop very bad, because of the huge data I want to process.
Any suggestion?
I tried right before execute:
foreach($this->array as $value){
if(!$value) {
$value = "";
}
}
It doesn't work.
The only way that solved my problem is modifying to this:
$this->array['cell1'] = !empty($value['cell1']) ? $value['cell1'] : "";
$this->array['cell2'] = !empty($value['cell2']) ? $value['cell2'] : "";
But this seems too rubbishy...
I know its necroposting, but maybe will help to someone.
You trying to check if the variable false, but not null. And not reapplying values back to array.
foreach($this->array as $value){
if(!$value) {
$value = "";
}
}
Try to check for null in loop
foreach($this->array as $index => $value)
{
$this->array[$index] = !empty($value) ? $value : '';
}
Your question has nothing to do with PDO but with basic PHP.
When there is no variable available - you can't use it at all. So, you have to create it somehow. The way you are using at the moment is not "rubbishy" but quite acceptable. I'd rather call whole code "rubbishy" as it's twice as big as as it should be.
But I need the referenced binding because of the loop, so bindValue isn't a solution.
This assumption is wrong too. Why do you think you can't use bind by value?
$sql = "INSERT INTO `table` (`coloumn1`, `coloumn2`) VALUES (?, ?)";
$sth = $db->prepare($sql);
foreach($data as $value)
{
$value['cell1'] = !empty($value['cell1']) ? $value['cell1'] : "";
$value['cell2'] = !empty($value['cell2']) ? $value['cell2'] : "";
$sth->execute($value);
}
as simple as this
And I need the loop very bad, because of the huge data I want to process.
I don't think it's really huge, as it fits for the PHP process memory. However, consider to use LOAD DATA INFILE query for the real huge amounts.

How is my PDO binding still messing up my query?

Below is a function designed to handle a search scenario for a custom class.
I've already tripped over the fact that PDO defaults to binding parameters as strings, causing an integer->string conversion even if it's not appropriate. As you'll see, I corrected that by manually checking if the type is integer and then forcing the use of int in those cases. Problem is, my solution only works for a 'start' value of 0 -- anything higher errors out, and I don't know why. If I manually set the start/count values to their appropriate values ( i. e. instead of :count I use {$count}), everything works fine, so it looks like the binding is still messing up.
How? Or if I'm wrong... what is right?
/*Query is:
SELECT tutor_school.id
FROM tutor_school, tutor_states
WHERE tutor_states.stateName=:state AND tutor_states.id=tutor_school.state
GROUP BY tutor_school.id order by tutor_school.name asc
LIMIT :start, :count*/
$db = Database::get_user_db();
$statement = $db->prepare($query);
foreach ($executeArray as $key => $value)
{
if (getType($value) == 'integer')
{
$statement->bindParam($key, $executeArray[$key], PDO::PARAM_INT);
}
else
{
$statement->bindParam($key, $value);
}
}
var_dump($executeArray);//count and start are still ints
if ($statement->execute())
{
var_dump($executeArray);//start and count are now strings
var_dump($statement->errorInfo());
var_dump($query);
$values = $statement->fetchAll();
$return = array();
foreach ($values as $row)
{
$school = School::schoolWithId($row[0]);
if (!empty($school))
{
$return[] = $school;
}
}
return $return;
}
Metadata (such as the LIMIT arguments) can't be parametrized. You will have to use (properly sanitized) interpolation instead.

Dynamic Prepared Statements - Does this open security holes?

This is similar to this question - Are Dynamic Prepared Statements Bad? (with php + mysqli), however since it is 4 years old I wanted to get a more upto date answer.
I've written a class which, although I haven't tested it on more copmlex sql queries, it has worked without fail on simple sql queries, however I'm not sure if doing so has bypassed one of the main reasons for prepared statements - security.
I have made use of the call_user_func_array which was easy enough with the bind_param statements however with the bind_result was a little trickier. I originally used get_result however the host I've gone with doesn't have mysqlnd available, but I managed to get around using the metadata. This is the full class I have written.
Do you think this is secure?
The passed in values are:
$sql is the passed in sql statement:
SELECT * FROM users WHERE id = ? AND created_timestamp > ?
$mysqli is the mysqli connection
$para is the placeholder in the prepared statement:
array ($types = 'ii', 23, 1235376000)
The class:
class crudModel {
function ps($sql, $mysqli, $para) {
//this function should work for just about any simple mysql statement
//for more complicated stuff like joins, unions etc,. we will see
if ($prep = $mysqli->prepare($sql)) {
call_user_func_array(array($prep, 'bind_param'), $this->makeValuesRef($para, $mysqli));
$prep->execute();
$meta = $prep->result_metadata();
while ($field = $meta->fetch_field()) {
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($prep, 'bind_result'), $parameters);
while ($prep->fetch()) {
foreach ($row as $key=>$val) {
$x[$key] = $val;
}
$data[] = $x;
}
return $data;
}
}
function makeValuesRef($array, $mysqli) {
$refs = array();
foreach($array as $key => $value) {
$array[$key] = $mysqli->real_escape_string($value); //i don't think escaping is necessary, but it can't hurt (??)
$refs[$key] = &$array[$key];
}
return $refs;
}
}
What you're doing here isn't a dynamic prepared statement. You're just putting some syntatic sugar on top of the MySQLi API (which sucks).
In short, there aren't really any security concerns present from the code you've shown here. In fact, this sort of practice is quite good, because it makes it easier to verify that you're doing it correctly later (since the MySQLi API sucks).
So you're fine. I would worry about the areas you're generating the queries, and ensuring that you're not accidentally putting user-data into them without white-listing...

Can someone help explain how this function works (mysqli prepared statment array function)

i just need a little help on how i could implement this function. i cant quite read this. I found this function to stick the results from a mysqli prepared statement query into an array which is what i am trying to do, but i am unsure how to use the function.
$meta = $statement->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($statement, 'bind_result'), $params);
while ($statement->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$hits[] = $c;
}
$statement->close();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
This looks at the result set and extracts the field names into an array.
call_user_func_array(array($statement, 'bind_result'), $params);
That array of field names is then used to auto-create variables of the same name as the field, to which the database results are bound to. This is very bad practice, as it auto-creates variables based on query results, and can potentially overwrite a variable you'd be using for other purposes elsewhere. You'd never know why, because there's no explicit assignment to that variable name to search for.
The while loop is highly inefficient, but retrieves each row from the query results and places them into an array called $hits.
So, basically, it's a gold plated piece of bovine excrement that impressively does very little.

Categories