Getting the url run by PDO when NOT using bindparam? - php

For many queries, it's faster and easier to just use the question-mark notation for a query. There are various guides and posts about getting the final run query by extending PDO, but I never see one that works when using the question-mark notation. Can this be done? I'm running a query that appears by all accounts to work, but it's not returning results in the actual PDO code for some reason. What does it take to actually get the EXACT final query that PDO is running so I can see where the error is?
$sth = $dbh->prepare("SELECT fromID,toID,m_key FROM messages WHERE (fromID = ? AND toID IN (?)) OR (toID = ? AND fromID IN (?)) AND message LIKE 'addfriend'");
$sth->execute(array($_SESSION['userID'],$include,$_SESSION['userID'],$include));

The problem is not with the use of the ? placeholder, but the fact that you try to bind a single variable to represent a list of variables in the in operator. You have to provide as many ? placeholders in the in operator separated by commas as the number of parameters you want to have there and you need to bind to each placeholder separately.
...fromID IN (?, ?, ?, ..., ?)...

The below method will help you to substitute the value ($params) against ? or :param placeholder. So that we can see the exact query to be executed.
Note: It is useful only for development debugging purpose.
public static function interpolateQuery($query, $params) {
$keys = array();
$values = $params;
# build a regular expression for each parameter
foreach ($params as $key => $value) {
if (is_string($key)) {
$keys[] = '/:'.$key.'/';
} else {
$keys[] = '/[?]/';
}
if (is_array($value))
$values[$key] = implode(',', $value);
if (is_null($value))
$values[$key] = 'NULL';
}
// Walk the array to see if we can add single-quotes to strings
array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));
$query = preg_replace($keys, $values, $query, 1, $count);
return $query;
}

Related

How can I pass many columns to bind_result() with more flexibility?

I have some long MySQL table whose design is not totally fixed yet. So, occasionally, I need to add/delete some columns. But, every time I alter the table, I must re-write all the line dealing with bind_result(). I am looking for a solution which makes this change easy.
Assume I currently have a table with columns like col_a, col_b, col_c, ..., col_z. So, I use the bind_result() to store result values as the manual says.
$res = $stmt->bind_result($a, $b, $c,..., $z);
But, if I change the table design, I must change parameters of all the lines dealing with this bind_result() to match the new MySQL table.
Is there any technique like following?
// Some php file defining constants
define("_SQL_ALL_COLUMNS", "\$a, \$b, \$c, \$d, ... \$z");
// Some SQL process in in other php files
stmt->execute();
$res = $stmt->bind_result(_SQL_ALL_COLUMNS);
So, I don't need to worry about a change of the number of the parameters in other files as long as I once define them correctly somewhere. Of course, I already found that my attempt in the previous example was not a right way.
Is there any good solution for this type of situation?
Use call_user_func_array() to dynamically set the number of parameters:
function execSQL($con, $sql, $params = null)
$statement = $con->prepare($sql);
if (!$statement){
// throw error
die("SQL ERROR: $sql\n $con->error");
}
$type = "";
$arg = array();
if ($params && is_array($params)){
foreach($params as $param){
if (is_numeric($param)){
$type .= 'd';
continue;
}
$type .= 's';
}
$arg[] = $type;
foreach($params as $param){
$arg[] = $param;
}
call_user_func_array(array($statement,'bind_param'), refValues($arg)); // php 7
}
$res = $statement->execute();
if (!$res){
die("Looks like the Execute Query failed.\n\nError:\n{$statement->error}\n\nQuery:\n{$sql}\n\nParams:\n{".implode(",", $arg)."}");
}
return $con->insert_id;
}
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) { //Reference is required for PHP 5.3+
$refs = array();
foreach($arr as $key => $value){
$refs[$key] = &$arr[$key];
}
return $refs;
}
return $arr;
}
You can use it by calling the function execSQL with an array of parameters:
$result = execSQL($connection,$sql,["a","b","c","..."]);
What this does is check the data type of the parameters and appends to the $type variable, which will then be passed to the bind method as first parameter.

Using bind for form with lot of inputs (PHP)

I have form with lot of inputs, and I'm trying to import them in database (mysql).
I want to use bind but trying to avoid writing all variables so many times. Probably I can't explain so good, so I will here is a code
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
if(!empty($firstName)&& !empty($lastName)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param('sss', $firstName, $lastName, $gender);
if($unos->execute()) {....
1.Well this is working fine , and it's not a problem, but now I want to add more inputs so I tried this
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('$firstName','$lastName','$gender');
$type='sss';
$param_list = implode(',', $param);
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (firstName,lastName,gender) VALUES (?,?,?)");
$unos->bind_param($type,implode(',', $param));
if($unos->execute()) {....
and it's not working. I get "Number of elements in type definition string doesn't match number of bind variables"...
I don't get it, because when I echo this implode thing I get what I need.
I'm pretty newbie with PHP, so help will be so precious. :)
You can try this:
if(isset($_POST['firstName']) && isset($_POST['lastName']) && isset($_POST['gender'])){
$firstName=trim($_POST['firstName']);
$lastName=trim($_POST['lastName']);
$gender=trim($_POST['gender']);
$param=array('firstName' => 's','lastName' => 's','gender' => 's');
if(!empty($param)) {
$unos = $db->prepare("INSERT INTO members (". implode(',', array_keys($param) .") VALUES (". implode(',', array_fill(0, count($param), '?')) .")");
foreach($param as $paramName => $paramType) {
$unos->bind_param($paramType, $paramName);
}
if($unos->execute()) {....
You can pus as many parameters to $param array. Key should the name of the db column, value is its type.
You will need a variable for each question mark, but you will also need to bind each of the parameters separately. In your current situation, you bind a comma-separated list of values as a single string parameter.
What about this? I attempted to make the entire code rely on a single array of fields. If you want extra fields, you can just add them to the array and the rest of the code should respond to it. I don't have a proper test environment at hand and I typed this code by heart, so sorry for any typos. :)
// The fixes list of allowed/expected fields. Other values are ignored.
$fields = array('firstname', 'lastname', 'gender');
// Check if each value exists, and put them in an array.
$paramvalues = array();
foreach ($fields as $field) do
{
if (!isset($_POST[$field]))
die("missing field $field");
$paramvalues[] = & $_POST[$field]; // Bind_param wants a ref value, hence `&`
}
// Build a list of fields for the dynamic query.
$fieldlist = implode($fields, ',');
// And a list of placeholders.
$paramlist = implode(array_fill(0, count($fields), '?'), ',');
// And a list of types, assuming all parameters are strings.
$paramtypes = str_pad('', count($fields), 's');
// Prepare the query
$unos = $db->prepare("INSERT INTO members ($fieldlist) VALUES ($paramlist)");
// Build an array of reference values to be passed to call_user_func_array:
$paramrefvalues = array();
$paramrefvalues[] = $paramtypes
foreach ($paramvalues as $value) do
{
$paramrefvalues[] = & $value;
}
// Call bind_param using this array of by-ref parameters
call_user_func_array(array($unos, 'bind_param'), $paramrefvalues);
This code is loosely based on this article

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.

MySQLI binding params using call_user_func_array

Please see below my code.
I am attempting to bind an array of paramenters to my prepared statement.
I've been looking around on the web and can see I have to use call_user_func_array but cannot get it to work. The error I get is:
"First argument is expected to be a valid callback, 'Array' was given"
I may be wrong but I'm assuming the first argument can be an an array and perhaps this error message is misleading. I think the issue is that my array is in someway at fault.
Can anyone see what I am doing wrong? Thanks.
$type = array("s", "s");
$param = array("string1","anotherstring");
$stmt = $SQLConnection->prepare("INSERT INTO mytable (comp, addl) VALUES (?,?)");
$params = array_merge($type, $param);
call_user_func_array(array(&$stmt, 'bind_param'), $params);
$SQLConnection->execute();
It must be like this:
//connect
$mysqli = new mysqli($host, $user, $password, $db_name);
//prepare
$stmt = $mysqli->prepare("SELECT * FROM the_table WHERE field1= ? AND Field2= ?");
//Binding parameters. Types: s = string, i = integer, d = double, b = blob
$params= array("ss","string_1","string_2");
//now we need to add references
$tmp = array();
foreach($params as $key => $value) $tmp[$key] = &$params[$key];
// now us the new array
call_user_func_array(array($stmt, 'bind_param'), $tmp);
$stmt->execute();
/* Fetch result to array */
$res = $stmt->get_result();
while($row = $res->fetch_array(MYSQLI_ASSOC)) {
$a_data[]=$row;
}
print_r($a_data);
$stmt->close();
Since PHP 5.6, you don't have to mess around with call_user_func_array() anymore.
Instead of:
$stmt->bind_param($param_types, $my_params_array);
you can just use the splat operator, like this:
$stmt->bind_param($param_types, ...$my_params_array); // exact code
I wouldn't know why you have to use call_user_func_array, but that's another story.
The only thing that could be wrong in my eyes is that you are using a reference to the object. Assuming you're using PHP 5.*, that is not necessary:
call_user_func_array(array($stmt, 'bind_param'), $params);
If you get an error, you should try this:
call_user_func_array(array($stmt, 'bind_param'), refValues($params));
function refValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
Wasn't able to answer this on my own question because it got marked as dupe: here. But I think my final solution, which uses the answers in this question, works in my use case, might be helpful for someone.
My goals was to take a posted set of ID's and use them in a NOT IN MYSQL statement. Assuming array of 5 ID's posted.
Count the number posted ID's to build the ? placeholders for NOT IN statement. Using $params_count = substr(str_repeat(',?', count($array_of_ids)), 1); gives the result: (?,?,?,?,?) to be used in SQL statement.
Make function that takes ID's and type i or s etc. For me, they were all i so my function is simpler. return array that looks like this $params= array("iiiii",1,2,3,4,5) where the first value is the set of i's and the subsequent values are the ID's depending on total ID's passed into function.
function build_bind_params($values, $bind_type) {
$s = substr(str_repeat($bind_type, count($values)), 0);
$bind_array = array();
$bind_array[] = $s;
foreach($values as $value) {
$bind_array[] = $value;
}
return $bind_array;
}
$params = build_bind_params($array_of_ids, "i");
Then use foreach ($params as $key => $value) $tmp[$key] = &$params[$key]; to get the newly created $params formatted properly for binding.
Then use call_user_func_array(array($stmt , 'bind_param') , $tmp); to properly bind the array.
Then execute the $stmt
Most of the above are not solutions without integrating the types along with the values before adding them to call_user_func_array(). This solution worked for me:
/* create a database connection */
$db = new mysqli($host, $user, $password, $db_name);
/* setup the sql, values, and types */
$sql="SELECT * FROM languages
WHERE language_code = ?
AND charset = ?
ORDER BY native_name";
$values = array($langCode, $charset);
$types = "ss";
/* pass those variables to the execute() function defined below */
if ($rows = execute($sql, $values, $types))
{
return $rows[0];
}
function execute($sql, $values='', $types='')
{
/* prepare the sql before binding values and types */
$stmt = $db->prepare($sql);
/*combine the values and types into $inputArray */
$inputArray[] = &$types;
$j = count($values);
for($i=0;$i<$j;$i++){
$inputArray[] = &$values[$i];
}
/* add the combined values and types to call_user_func_array() for binding */
call_user_func_array(array($stmt, 'bind_param'), $inputArray);
$result = $stmt->execute();
return $result;
}
Here's a reference to the full description this example is based on:
http://big.info/2015/08/php-use-call_user_func_array-for-variable-number-of-parameters-arrays-in-prepared-statements.html
Why would you want to call call_user_func_array(array($statement, 'bind_param'), $bind_arguments)? Because $bind_arguments is an array. You get to have one function that binds a statement to its queried parameters, no matter how many parameters you'd have otherwise.
Example of good code...
<?php
# link
$dblink = new mysqli('HOSTNAME','USERNAME','PASSWORD','DATABASENAME');
# example data
$statement = $dblink->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
$recordvalues = ['John', 'H.', 'Smith', 25];
$sqlbindstring = "sssi"; # String, String, String, Integer example
# make the references
$bind_arguments = [];
$bind_arguments[] = $sqlbindstring;
foreach ($recordvalues as $recordkey => $recordvalue)
{
$bind_arguments[] = & $recordvalues[$recordkey]; # bind to array ref, not to the temporary $recordvalue
}
# query the db
call_user_func_array(array($statement, 'bind_param'), $bind_arguments); # bind arguments
$statement->execute(); # run statement
$result = $statement->get_result(); # get results
# get the results
if($result) {
while ($row = $result->fetch_assoc()) {
print("\n\nMy row is...");
print_r($row);
}
}
?>
Example of bad code...
<?php
# Same setup as above..
$statement->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
$statement->bind('John', 'H.", 'Smith', 25);
?>
In the first example: You can pass as much or as little to the binding to be done, so that bind() might be called in only one line in your entire application. This scales well.
In the second example: You must write one bind() statement for every possible group of insertions for every possible record in your database. This scales poorly.

Categories