PDO: can I avoid bindParam? - php

I modified this code from somewhere but I am not sure if I am doing it correctly,
I use method to insert data into database,
# insert or update data
public function query($query, $params=array())
{
try
{
$stmt = $this->connection->prepare($query);
$params = is_array($params) ? $params : array($params);
$stmt->execute($params);
return true;
}
catch (PDOException $e)
{
# call the get_error function
$this->get_error($e);
}
}
Then I just need to call it like this,
$sql = "
INSERT root_countries_cities_towns (
tcc_names,
cny_numberic,
tcc_created
)VALUES(
?,
?,
NOW()
)";
$pdo->query($sql,array('UK','000'));
It works fine perfectly! but I don't understand what this line does - can someone explain please?
$params = is_array($params) ? $params : array($params);
I thought I have to use bindParam to bind the parameters first, but it seems that I don;t have to anymore with is method - is it safe and secure then??
Does it meant that I don't have to prepare the query in this way anymore?
$sql = "
INSERT root_countries_cities_towns (
tcc_names,
cny_numberic,
tcc_created
)VALUES(
:name,
:numberic,
NOW()
)";
and forget about this binding?
$stmt = bindParam(':name','UK', PDO::PARAM_STR);
$stmt = bindParam(':numberic','000', PDO::PARAM_STR);
Thanks.

I guess that's pretty much PHP syntax question rather than PDO one.
$params = is_array($params) ? $params : array($params);
is a shortland (called ternary operator)) for
if (is_array($params)) {
$params = $params;
} else {
$params = array($params);
}
which I'd rather wrote as
if (!is_array($params)) $params = array($params);
which is pretty self-explanatory and can be read almost in plain English:
if $params is not an array, let's make it array with one value of former $params
That's why I hate ternary operator (and lambdas) and always avoid it's use. It makes pretty readable code into a mess. Just out of programmer's laziness.
To answer your other questions,
Does it meant that I don't have to prepare the query in this way anymore?
Who said that? You're preparing it all right in your code, check it again.
and forget about this binding?
that's true. execute($params) is just another way to bind variables.

the line
$params = is_array($params) ? $params : array($params);
is simply checking if the $params variable is an array, and if so, it creates an array with the original $params value as its only element, and assigns the array to $params.
This would allow you to provide a single variable to the query method, or an array of variables if the query has multiple placeholders.
The reason it doesn't use bindParam is because the values are being passed to the execute() method. With PDO you have multiple methods available for binding data to placeholders:
bindParam
bindValue
execute($values)
The big advantage for the bindParam method is if you are looping over an array of data, you can call bindParam once, to bind the placeholder to a specific variable name (even if that variable isn't defined yet) and it will get the current value of the specified variable each time the statement is executed.

The first example transforms the contents of your $params into an array, if it wasn't already an array (for example if only one parameter was passed and it was passed as an individual item instead of as an array of length 1).
The two examples work just as well, except that for the first one, parameters introduced with the array $params are injected where ? are found in the SQL query, whereas in the second one, the formatting of the parameters is actually done by name (you bind a parameter name as found in the sql to an actual parameter).
You should use the first one, it's easier to write.

Passing an array to PDOStatement::execute() passes each entry in the array through PDOStatement::bindParam() (or maybe bindValues()) using defaults (bind type, etc).
Basically, it's as safe as pre-binding.
The advantage to using bindParam is that it binds to the variable reference. This means you can change the value of the variable without re-binding and execute the statement with new values. This is especially useful in a loop, eg
$vals = array('foo', 'bar', 'baz');
$stmt->bindParam(1, $val);
foreach ($vals as $val) {
$stmt->execute(); // Executes once for each value in $vals
}
I don't understand what this line does - can someone explain please?
That line converts a non-array into an array.
For example
$params = 'foo';
$params = is_array($params) ? $params : array($params);
$params == array('foo');
I imagine it's to facilitate situations where you have only one placeholder and one value to bind as PDOStatement::execute() can only be passed an array.

Try this class. I use PDO a lot and this is what I use all the time for my projects.PHP PDO Class on GitHub

Related

mysqli_stmt_bind_param - expects references [duplicate]

This question already has answers here:
mysqli bind_param() expected to be a reference, value given
(3 answers)
Closed 12 months ago.
I understand that we have to pass references to mysqli_stmt_bind_param. I am doing the following
$ref = refValues($data);
function refValues($arr){
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
var_dump(implode(",", $refs));
return $refs;
return $arr;
}
I am having all of my values in an array. I am using the above function to get the references. Got the above answer from SO
My PHP version is 5.6
I am binding the params in the following way.
mysqli_stmt_bind_param($stmt, $types, $ref);
$stmt is a statement created through mysqli_prepare. It returns error number 0.
$types is nothing but $types = str_pad('',count($data),'s');
I have verified $types data also. It returns expected number of types. i.e ssssssss
If I execute, I am getting the following error.
Only variables should be passed by reference in test.php
I found this solution in SO. I cannot assign 100 variables. I am not thinking that is feasible.
I found another alternative is call_user_func_arrary.
$values = refValues($data);
call_user_func_array(array($stmt, 'bind_param'), $values);
It returns number of bind type doesn't match number of values. It is weird for me. I have verified the array and values. Both counts are matching. I am not aware of internal implementation of call_user_func_array.
Please let me know is there any way to solve this efficiently.
This line
mysqli_stmt_bind_param($stmt, $types, $ref);
means that you have one reference to bind.
Why? Let's see:
first argument is a statement
second argument is a string with types
following arguments are references to values which should be binded.
As you pass one argument (it is $ref) - you are trying to bind only one value. And $ref is not a reference, it is array of values which are refernces. See the difference? Array of references vs reference.
So, you took second approach, and it is a right one:
$values = refValues($data);
call_user_func_array(array($stmt, 'bind_param'), $values);
What's the error here? You didn't pass types $types:
// do not assign anything to a variable
// pass results of `refValues` directly to `call_user_func_array`
call_user_func_array(array($stmt, 'bind_param'), array_merge(array($types), refValues($data)));
What do we do here: we are trying to call $stmt->bind_param and pass to this function arguments as array.
What are the arguments of $stmt->bind_param?
first argument is types ($types)
following arguments are references to values ($values)
Now it should work.
There are two possible ways to avoid this hassle:
Use PDO. Your current problem is only the first out of many WTFs you will have with mysqli. In this particular case it would be as simple and natural as
$stmt = $db->prepare($sql);
$stmt->execute($data);
Okay, you have such a whim of using mysqli. Then, as long as you are using a supported PHP version, you can use a splat or a three dot operator:
$stmt = $db->prepare($sql);
$types = str_repeat('s', count($data));
$statement->bind_param($types, ...$data);
$statement->execute();

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();
}

Pass form field names to bind parameter at class instance

I'm using a factory(class) to present forms from a target database table - as defined at class instance. Then on submit, create a new instance of the class which then insert a new record in to the database. $_POST key names match the table column names.
My issue is dynamically assigning bind parameters when the variables are determined at class instance. I'm getting the following, whether I use Reflections method or inline.
Warning: mysqli_stmt::bind_param() [mysqli-stmt.bind-param]: Number of elements in type definition string doesn't match number of bind variables
The following method is called in the sub class after the post array has been contructed and assigned to the class property $array.
private function addrecord($array,$tbl,$_conn){
//define field name array for query statement
foreach ($array as $key=>$value){
$keyarr[]=$key;
}
//BUILD THE QUERY STATEMENT
$query = "INSERT INTO $tbl SET ";
foreach ($keyarr as $key){
$query .= ($key."=?, "); //clone and add next element
}
$query = rtrim($query,", "); //remove EOL whitespace and comma
//done
/*
//Hard code bind parameters works as expected
if (self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME)){
$stmt=self::$_conn->prepare($query);
$stmt->bind_param("sssss",$array['user_id'],$array['user_name'],$array['user_email'],$array['user_date'],$array['user_active']);
$stmt->execute();
$insertid=$stmt->insert_id;
$stmt->close();
echo "The record was created with id ".$insertid;
}
*/
//Tried re assigning post array as reference
//same error as just passing $array
//$array = $this->refValues($array);
//Binding params using Reflections, same error
self::$_conn = new mysqli(DB_HOST,DB_UNAME,DB_UPWORD,DB_NAME);
$stmt = self::$_conn->prepare($query);
$ref = new ReflectionClass('mysqli_stmt');
$method = $ref->getMethod("bind_param");
$method->invokeArgs($stmt,$array);
$stmt->execute();
$stmt->close();
self::$_conn->close();
}
//Pass By Reference required for PHP 5.3+, dev server 5.3.17
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0){
$refar = array();
foreach($arr as $key => $value)
$refar[$key] = &$arr[$key];
return $refar;
}
return $arr;
}
Thanks in advance and much appreciated.
As you can see, mysqli is practically unusable with prepared statements.
So, I'd suggest you to either use PDO or, better, some intelligent library that can make safe query without prepared statments.
With such a library your function will be written in one line
private function addrecord($array,$tbl){
$this->conn->query("INSERT INTO ?n SET ?u", $tbl, $array);
}
please note that if $array is coming from the untrusted source, you have to filter it's content out first.
Per Common Sense, changed process to PDO. Works as expected. Should have done it sooner. Only issue remaining is UI feedback. Would like to return an insert id, however MySQL doesnt return a last insert id for PDO. And again, the class doesnt know the tables structure in advance so hard coding in not an option. Need a workaround. Any thoughts? Heres the new insert process using PDO.
try{
self::$_conn = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.'', DB_UNAME, DB_UPWORD);
$stmt = self::$_conn->prepare($query);
$arcount=count($array); //set number of bindParam loops
foreach($array as $key=>$value){
$stmtarr[]=$value; //convert assoc to numerated array
}
//re index array so increment will match up with placeholder position
$stmtarr = array_combine(range(1, count($stmtarr)), array_values($stmtarr));
for($i=1;$i<=$arcount;$i++){ //bind variable, one for each placeholder,
$stmt->bindParam($i,$stmtarr[$i]);
}
$stmt->execute();
} catch (PDOException $e){
print "Error: ".$e->getMessage()."<br>";
die();
}
Assume everything else is the same as above.

Retrieving PHP Prepared Statement field data

I am using the Mysqli extension and a PHP Prepared Statement SELECT; I don't know how many fields I have in the SELECT until I after I do the
$stmt->execute();
$fieldcnt = $stmt->field_count;
Because this, I am having problems doing a
$stmt->bind_result(list of parms);
Namely, since I don't know how many fields have been returned, I don't know how to contruct the "list of parms."
So, I need some advice on how I access the fields returned;
Don't use bind_result, and use fetch_assoc().
This will return an associated array for each row:
while ($row = $stmt->fech_assoc()) {
print_r($row);
}
You can leverage call_user_func_array() to accomplish this task -- if you absolutely must use bind_result().
// create an array the size of the number of parameters
$params = array_fill(0, $fieldcnt, null);
// call bind_result, resulting in every column of the result to be
// bound to a value in $params
call_user_func_array(array($stmt, 'bind_result'), $params);
// take a look at all the params
print_r($params);

Variable parameter/result binding with prepared statements

In a project that I'm about to wrap up, I've written and implemented an object-relational mapping solution for PHP. Before the doubters and dreamers cry out "how on earth?", relax -- I haven't found a way to make late static binding work -- I'm just working around it in the best way that I possibly can.
Anyway, I'm not currently using prepared statements for querying, because I couldn't come up with a way to pass a variable number of arguments to the bind_params() or bind_result() methods.
Why do I need to support a variable number of arguments, you ask? Because the superclass of my models (think of my solution as a hacked-up PHP ActiveRecord wannabe) is where the querying is defined, and so the find() method, for example, doesn't know how many parameters it would need to bind.
Now, I've already thought of building an argument list and passing a string to eval(), but I don't like that solution very much -- I'd rather just implement my own security checks and pass on statements.
Does anyone have any suggestions (or success stories) about how to get this done? If you can help me solve this first problem, perhaps we can tackle binding the result set (something I suspect will be more difficult, or at least more resource-intensive if it involves an initial query to determine table structure).
In PHP you can pass a variable number of arguments to a function or method by using call_user_func_array. An example for a method would be:
call_user_func_array(array(&$stmt, 'bindparams'), $array_of_params);
The function will be called with each member in the array passed as its own argument.
The more modern way to bind parameters dynamically is via the splat/spread operator (...).
Assuming:
you have a non-empty array of values to bind to your query and
your array values are suitably processed as string type values in the context of the query and
your input array is called $values
Code for PHP5.6 and higher:
$stmt->bind_param(str_repeat('s', count($values)), ...$values);
In fact, all of the arguments fed to bind_param() can be unpacked with the splat/spread operator if you wish -- the data types string just needs to be the first element of the array.
array_unshift($values, str_repeat('s', count($values)));
$stmt->bind_param(...$values);
You've got to make sure that $array_of_params is array of links to variables, not values themselves. Should be:
$array_of_params[0] = &$param_string; //link to variable that stores types
And then...
$param_string .= "i";
$user_id_var = $_GET['user_id'];//
$array_of_params[] = &$user_id_var; //link to variable that stores value
Otherwise (if it is array of values) you'll get:
PHP Warning: Parameter 2 to mysqli_stmt::bind_param() expected to be a reference
One more example:
$bind_names[] = implode($types); //putting types of parameters in a string
for ($i = 0; $i < count($params); $i++)
{
$bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
$$bind_name = $params[$i]; //create a variable with this name and put value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
}
and BOOOOOM:
call_user_func_array( array ($stmt, 'bind_param'), $bind_names);
I am not allowed to edit, but I believe in the code
call_user_func_array(array(&$stmt, 'bindparams'), $array_of_params);
The reference in front of $stmt is not necessary. Since $stmt is the object and bindparams is the method in that object, the reference is not necessary. It should be:
call_user_func_array(array($stmt, 'bindparams'), $array_of_params);
For more information, see the PHP manual on Callback Functions."
call_user_func_array(array(&$stmt, 'bindparams'), $array_of_params);
Didn't work for me in my environment but this answer set me on the right track. What actually worked was:
$sitesql = '';
$array_of_params = array();
foreach($_POST['multiselect'] as $value){
if($sitesql!=''){
$sitesql .= "OR siteID=? ";
$array_of_params[0] .= 'i';
$array_of_params[] = $value;
}else{
$sitesql = " siteID=? ";
$array_of_params[0] .= 'i';
$array_of_params[] = $value;
}
}
$stmt = $linki->prepare("SELECT IFNULL(SUM(hours),0) FROM table WHERE ".$sitesql." AND week!='0000-00-00'");
call_user_func_array(array(&$stmt, 'bind_param'), $array_of_params);
$stmt->execute();

Categories