Dynamic prepared statement (bind param error) - php

I'm trying to be able to add parameters to to my prepared statement, query and arrays look right. But the "Number of elements in type definition string doesn't match number of bind variables" error is triggered.
$sql = 'SELECT * FROM `feed` ';
$types = array();
$params = array();
if( isset($_GET['p']) ) {
$page = $_GET['p'];
}
else {
$page = 0;
}
if( isset($_GET['q']) ) {
$sql .= 'WHERE `title` LIKE ? ';
$search = $_GET['q'];
array_push($types, 's');
array_push($params, $search);
}
$sql .= 'ORDER BY `time` DESC LIMIT ?, 6';
array_push($types, 'i');
array_push($params, $page);
$stmt = $mysqli->prepare($sql);
$params = array_merge($types, $params);
$refs = array();
foreach($params as $key => $value)
$refs[$key] = &$params[$key];
call_user_func_array(array($stmt, 'bind_param'), $refs);
(Printed from the server)
Query: SELECT * FROM feed WHERE title LIKE ? ORDER BY time DESC LIMIT ?, 6
Array merge:
Array
(
[0] => s
[1] => i
[2] => word
[3] => 0
)
Thanks.

My understanding is that the first parameter 'types' is a string of the types of the parameters, not an array. so the the parameter list for the example should look like:
Array
(
[0] => si
[1] => word
[2] => 0
)
This is untested code: but implode should do what we want from the '$types' array
$strTypes = implode('', $types);
i will check it later.

Related

PHP convert columns to query

I have a table on the frontend, where the user can choose what types of columns he wants.
After submitting the form I get the array with selected columns.
For instance the user select following columns:
$columns = ["campaign_start_time", "campiagn_end_time", "campaign_id", "campaign_budget", "adset_name", "ad_name"]
Now I have to make the request to the facebook api, but facebook api only request query in form:
$query = "campaign{start_time, end_time, id, budget}, adset{name}, ad{name}"
Here is my question, what is the best way to convert $columns to $query in PHP language?
If you can construct your submitted data to be in this form:
Array
(
[campaign] => Array
(
[0] => start_time
[1] => end_time
[2] => id
[3] => budget
)
[adset] => Array
(
[0] => name
)
[ad] => Array
(
[0] => name
)
)
Maybe using inputs or other constructs such as:
name="campaign[]" value="start_time"
name="campaign[]" value="end_time"
Then looping and building the query with the keys and values will do it:
foreach($columns as $key => $val) {
$query[] = $key . '{' . implode(',', $val) . '}';
}
$query = implode(',', $query);
Otherwise you'll need to parse it to get what you need first, then execute the loop above using $result instead:
foreach($columns as $val) {
preg_match('/^([^_]+)_(.*)/', $val, $match);
$result[$match[1]][] = $match[2];
}
This solution splits apart the values based on underscores and then joins them back together as nested values based on the category/key. Note that this doesn't check for items that don't have a "prefix".
<?php
$columns = ["campaign_start_time", "campaign_end_time", "campaign_id", "campaign_budget", "adset_name", "ad_name"];
$criteria = [];
foreach( $columns as $column ){
$pieces = explode('_',$column);
$category = array_shift($pieces);
if( !isset($criteria[$category]) ){
$criteria[$category] = [];
}
$criteria[$category][] = implode('_', $pieces);
}
$queryPieces = [];
foreach($criteria as $category => $values){
$queryPieces[] = $category.'{'.implode(', ', $values).'}';
}
$query = implode(', ', $queryPieces);

How to use variable as parameter name in PHP SQLite3 bindparam

I am using SQLite3 and PHP, and try to write a generic function to execute my queries.
For this I would like to retrieve the name of the parameters from variables in the bindParam.
But it doesn't seem to work. Here is a code showing the unexpected behavior:
<?php
header("Content-Type: text/plain");
$db = new SQLite3(':memory:');
$db->exec("create table mytable (lsid integer primary key autoincrement, usid TEXT, source TEXT)");
$myid = 'agoodid';
$mydatasources = array('home', 'news');
foreach($mydatasources as $datasource) {
$params[] = array(':datasource' => $datasource, ':usid' => $myid);
}
echo "here are the inputs : " .PHP_EOL;
print_r($params);
$querystring = 'insert into mytable (source, usid) values (:datasource, :usid)' ;
echo " I prepare the query '$querystring'" . PHP_EOL;
$query = $db->prepare($querystring);
foreach($params as $set) {
foreach($set as $key => $value) {
echo " Setting $key = $value." . PHP_EOL;
$query->bindParam($key, $value, SQLITE3_TEXT);
}
echo " I execute the query" . PHP_EOL;
$queryres[] = $query->execute();
$query->reset();
}
$results = $db->query('select * from mytable');
echo "and here is what I get : " . PHP_EOL;
while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
print_r($row);
}
?>
And here is the final result :
Array
(
[lsid] => 1
[usid] => agoodid
[source] => agoodid
)
Array
(
[lsid] => 2
[usid] => agoodid
[source] => agoodid
)
All the parameters seem to be bound with the value of the last bound parameter.
The expected result is:
Array
(
[lsid] => 1
[usid] => agoodid
[source] => home
)
Array
(
[lsid] => 2
[usid] => agoodid
[source] => news
)
How to do this? As a reminder: the aim is to not hard code the name of the parameter in bindParam.
I got the binParam function wholy wrong. it is attaching to the given parameter in the sql query a reference to the given variable in php.
I have to use bindValue instead.

Loop through array in bind parameters - prepared statement

I am writing a function in which I can do a couple of database actions, in this case an insert data based on a simple array
"insert" => array (
"1" => array (
"tnt_role" => array (
"rolename" => array (
"value" => "administrator",
"notation" => "string"
)
)
),
"2" => array (
"tnt_role" => array (
"rolename" => array (
"value" => "user",
"notation" => "string"
)
)
),
"3" => array (
"tnt_users" => array (
"username" => array (
"value" => "administrator",
"notation" => "string"
),
"userpassword" => array (
"value" => md5('admin', FALSE),
"notation" => "string"
),
"email" => array (
"value" => "someone#something.com",
"notation" => "string"
),
"roleid" => array (
"value" => "1",
"notation" => "int"
)
)
)
)
and here is the specific part of the function
case "insert":
foreach ($tables as $instance => $inserttables) {
foreach ($inserttables as $table => $fields) {
// create a count on the number of fields that are being parsed
$countfields = count($fields);
$sql = "INSERT INTO ". $table ." (" ;
$i = 0;
// set up the columns for the insert statement
foreach ($fields as $field => $value) {
$i++;
$sql .= $field;
if ($countfields != $i ) {
$sql .= ", ";
}
}
// close the column statement, open the value statement, since this is prepared, we will add question marks and add later the values
$sql .= ") ";
$sql .= "VALUES (";
$i = 0;
$parameters = "";
$notation = "";
foreach ($fields as $field => $value) {
$i++;
$sql .= "?";
// set up the notation in the bind parameters
switch($value['notation']) {
case "int":
$notation .= "i";
break;
case "string":
$notation .= "s" ;
break;
}
// need to escape the email and username values
$parameters .= "'".$value['value']."'" ;
if ($countfields != $i ) {
$sql .= ", ";
$parameters .= ", ";
}
}
$sql .= ")";
$stmt = mysqli_prepare($con, $sql);
mysqli_stmt_bind_param($stmt, $notation, $parameters);
if(mysqli_stmt_execute($stmt)) {
echo "data entered";
} else {
echo "error in following query:". $sql;
}
}
}
break;
This works all fine except for 1 tiny thing and that is when I enter more than 1 item in the database. It gives me the following error
mysqli_stmt_bind_param(): Number of elements in type definition string
doesn't match number of bind variables in .... line 647
I realized after a while that it is the parameter variable that is the case. The bind parameter here is only 1 variable in which I separate it all nicely with a comma (in order to mimic the list). Viewing this optical would say this looks fine, however I think the bind parameter statement really requires separate variables. At this point it sees actually just one variable, rather than the 4 in my test case.
I tried looping it this way:
mysqli_stmt_bind_param($stmt, $notation,
foreach ($fields as $field => $value) {
echo $value['value'];
if ($countfields != $i ) {
echo ",";
}
}
);
But to no avail, since it will spit out the following.
Parse error: syntax error, unexpected 'foreach' (T_FOREACH) in
Does anybody have an idea how to solve this issue?
== edit ==
table structure as requested, although I doubt it is that problem, since I get a bind parameter error, not an error in executing the statement.
== edit 2 ==
also tried the following, which didn't help, since it didn't stack (I saw this in PDO)
foreach ($fields as $field => $value) {
switch($value['notation']) {
case "int":
$notation = "i";
break;
case "string":
$notation = "s" ;
break;
}
mysqli_stmt_bind_param($stmt, $notation, $value['value']);
}
You need to pass each variable individually to mysqli_stmt_bind_param, so $parameters needs to be an array, not a string. Change the following lines of code:
$parameters = "";
to:
$parameters = array();
and
$parameters .= "'".$value['value']."'" ;
to:
$parameters[] = $value['value'];
(note there is no need to escape values when you are using prepared statements)
remove this line:
$parameters .= ", ";
and finally, change
mysqli_stmt_bind_param($stmt, $notation, $parameters);
to:
mysqli_stmt_bind_param($stmt, $notation, ...$parameters);
and it should work fine.

MySQLi: Number of variables doesn't match number of parameters in prepared statement

I'm trying to pass a prepared query through a function that prepares the query and executes but I keep getting "Number of variables doesn't match number of parameters in prepared statement".
I cannot figure out what's wrong because I have the same number of variables as parameters.
Query:
UPDATE `users` SET fullname = ?, telephone = ?, email = ?, company_name = ?, company_addr2 = ?, company_town = ?, company_postcode = ?, sra_registration = ?, company_number = ?, vat_registration = ?, company_website = ?, list_of_partners = ?, my_staff = ? WHERE id = ?
Params:
Array
(
[0] => Ben Shepherd
[1] => 071111111111
[2] => john.doe#domain.com
[3] => Company name
[4] => addr2
[5] => town
[6] => postcode
[7] => 123456
[8] => 123456
[9] => 123465
[10] => http://www.somewebsite.com
[11] => United Kingdom
[12] => "[{\\\"staff_firstname\\\":\\\"John\\\",\\\"staff_surname\\\":\\\"Smith\\\",\\\"staff_email\\\":\\\"some#email.com\\\"},{\\\"staff_firstname\\\":\\\"Jane\\\",\\\"staff_surname\\\":\\\"Smith\\\",\\\"staff_email\\\":\\\"some2#email.com\\\"},{\\\"staff_firstname\\\":\\\"John 3\\\",\\\"staff_surname\\\":\\\"Smith 3\\\",\\\"staff_email\\\":\\\"john.smith3#email.com\\\"}]"
[13] => 1
)
Query function:
public static function query($query, $params = array())
{
$params = !is_array($params) ? array($params) : $params;
$data = array();
$stmt = self::$i->prepare($query);
if(!$stmt)
return false;
foreach($params as $value)
{
/* Bind parameters. Types: s = string, i = integer, d = double, b = blob */
switch( true )
{
case (is_double($value)):
$type = 'd';
break;
case (is_int($value)):
$type = 'i';
break;
default:
case (is_string($value)):
$type = 's';
break;
}
$stmt->bind_param($type, $value);
}
$stmt->execute();
$res = $stmt->get_result();
if(!$res) return false;
while($row = $res->fetch_array(MYSQLI_ASSOC) )
{
$data[] = $row;
}
self::$customError = "";
return $data;
}
Try to put all parameters into one bind_param call.
So move the $stmt->bind_param($type, $value); out of the foreach loop by using call_user_func_array:
<?php
public static function query($query, $params = array())
{
$params = !is_array($params) ? array($params) : $params;
...
$types = array();
foreach($params as $value)
{
/* Bind parameters. Types: s = string, i = integer, d = double, b = blob */
switch( true )
{
case (is_double($value)):
$type = 'd';
break;
case (is_int($value)):
$type = 'i';
break;
default:
case (is_string($value)):
$type = 's';
break;
}
$types .= $type;
}
call_user_func_array(array($stmt, 'bind_param'), array_merge(array($types), $params));

Regex to replace unless in comment

I am trying to write a PHP function that can (given an SQL query with named-printf-style parameters, and an associative array) replace named parameters with a question mark (for SQL prepared statements) and rearrange the array so that the arguments are in the order that they appear in the query.
I have written the following code, which works, however I would like to extend this code so as to ignore named parameters that appear within comments.
function sqlprintfn($sql, array $args = array()) {
$ordered_args = array();
static $formats = array('b','c','d','e','E','u','f','F','g','G','o','s','x','X');
$regex = sprintf('/(\?\(([a-zA-Z_]\w*)\)([%s]))/', implode('', $formats));
// Find the next named argument. Each search starts at the end of the previous replacement
for ($pos = 0; preg_match($regex, $sql, $match, PREG_OFFSET_CAPTURE, $pos);) {
$arg_pos = $match[0][1];
$arg_len = strlen($match[0][0]);
$arg_key = $match[2][0];
$arg_format = $match[3][0];
// Programmer did not supply a value for the named argument found in the format string
if (!array_key_exists($arg_key, $args)) {
trigger_error(sprintf('%s(): Missing argument \'%s\'', __FUNCTION__, $arg_key), E_USER_WARNING);
return false;
}
array_push($ordered_args, $args[$arg_key]);
// Replace the named argument with a question mark
$sql = substr_replace($sql, $replace = '?', $arg_pos, $arg_len);
$pos = $arg_pos + strlen($replace); // skip to end of replacement for next iteration
}
return array_merge((array) $sql, $ordered_args);
}
As an example of what I am trying to achieve:
$sql = 'SELECT id FROM users WHERE username = ?(username)s AND type = ?(type)s';
$params = array('type' => 'admin', 'username' => 'bob', email => 'bob#domain.com');
print_r(sqlprintfn($sql, $params));
Should output:
Array
(
[0] => 'SELECT id FROM users WHERE username = ? AND type = ?',
[1] => 'bob',
[2] => 'admin'
)
And:
$sql = 'SELECT id FROM users WHERE /* email = ?(email)s AND */ username = ?(username)s AND type = ?(type)s';
$params = array('type' => 'admin', 'username' => 'bob', email => 'bob#domain.com');
print_r(sqlprintfn($sql, $params));
Should output:
Array
(
[0] => 'SELECT id FROM users WHERE /* email = ?(email)s AND */ username = ? AND type = ?',
[1] => 'bob',
[2] => 'admin'
)
Also, note that the $sql variable may be multi-lined.

Categories