PHP Using call_user_func_array to bind_param - php

i had problem INSERT data to mysql using PHP OOP.
so, this is my code :
public function insert($setType, $setTable, $setRow, $setValues) {
$change = function($values) {
return "?";
};
$row = join(",", $setRow);
$done = join(",", array_map($change, $setValues));
$values[] = join(",", $setValues);
function SqlArrayReferenceValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
$insert = $this->connect->prepare("INSERT INTO $setTable ($row) VALUES ($done)");
$mergedValues = array_merge(array($setType), $values);
call_user_func_array(array($insert, "bind_param"), SqlArrayReferenceValues($mergedValues));
$insert->execute();
$insert->close();
return $insert;
}
if (empty($_SESSION['transaction'])) :
$idt = date("ymdhis");
$_SESSION['transaction'] = $idt;
endif;
$st = $_SESSION['transaction'];
if (isset($_SESSION['email'])) :
$se = $_SESSION['email'];
$user = $objMysql->query(array("*"), "user", "email = '$se'");
$dataUser = $objMysql->fetch($user);
$ide = $dataUser['id_user'];
else :
$ide = 0;
endif;
$currentLink = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$objMysql->insert("sis", "url", array("id_transaction", "id_user", "url"), array("$st", "$ide", "$currentLink"));
this is my mysql table :
id_transaction row - type varchar.
id_user row - type INT
url row - type text
i got 1 errors,
mysqli_stmt::bind_param(): Number of elements in type definition string doesn't match number of bind variables in
what's wrong with my code?
FINAL SOLUTION BY MARTIN
public function SqlArrayReferenceValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
public function insert($setType, $setTable, $setRow, $setValues) {
$change = function($values) {
return "?";
};
$row = join(",", $setRow);
$done = join(",", array_map($change, $setValues));
$insert = $this->connect->prepare("INSERT INTO $setTable ($row) VALUES ($done)");
$params = $setValues;
$ww = array_merge(array($setType), $params);
call_user_func_array(array($insert, "bind_param"), $this->SqlArrayReferenceValues($ww));
$insert->execute();
$insert->close();
return $insert;
}

As correctly noted in comments, by Mark Baker and bub, you are passing a string value in the place of an array value.
What you have is an array of values you then turn into a string, before then trying to use them as an array. There is no need use join on the $values variable.
//$values = join(",", $setValues); // comment out this line,
call_user_func_array(array($insert, "bind_param"), array_unshift(array($setType), $setValues));
Here you use array_unshift to insert the array TYPES that you passed to the function, at the start of the array. This will now give you a correct array to insert into bind_param:
array("sis", $st, $side, $currentLink);
Problems with expected by reference:
There is an issue with the above that the MySQLi OOP code expects objects as references, so as well as the above- the values needs to be run through a separate process to give their "true" values to the call_user_func_array() function.
In your class/script add:
private function SqlArrayReferenceValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
This function takes the values and returns the correct value types to the call_user_array_function.
so:
$mergedValues = array_unshift($setValues, $setType);
call_user_func_array(array($insert, "bind_param"), $this->SqlArrayReferenceValues($mergedValues));
I got this code from somewhere on Stack Overflow and it's been very useful for years! This is the link: https://stackoverflow.com/a/16120923/3536236
Update 3:
How your code should look:
private function SqlArrayReferenceValues($arr){
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value)
$refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
public function insert($setType, $setTable, $setRow, $setValues) {
$change = function($values) {
return "?";
};
$row = join(",", $setRow);
$setValuesCopy = $setValues;
$done = join(",", array_map($change, $setValuesCopy));
$insert = $this->connect->prepare("INSERT INTO ".$setTable." (".$row.") VALUES (".$done.")");
$mergedValues = array_unshift($setValues, $setType);
//probably not needed but resetting array numerical indexing:
$mergedValues = array_values($mergedValues);
call_user_func_array(array($insert, "bind_param"), $this->SqlArrayReferenceValues($mergedValues));
$insert->execute();
$insert->close();
return $insert;
}
$currentLink = "http://".$_SERVER[HTTP_HOST].$_SERVER[REQUEST_URI]";
$objMysql->insert("sis", "url", array("id_transaction", "id_user", "url"), array($st, $ide, $currentLink));

Related

Wrong parameter count for bind_param in custom mysqli class

Trying to generate a custom mysqli class / wrapper with $this->_mysqli as a mysqli instance:
// Query string generator
private function gen_query($type, $data, $table){
switch ($type) {
case 'ins':
$query = "INSERT INTO " .$table .' ';
$query .= implode_key($opr= ', ', $data);
$query .= " VALUES " . value($data);
break;
case 'select':
// yet to generate
default:
$query ='';
break;
}
return $query;
}
// Generates bind parameters
private function gen_param($data){
$_param = "'";
foreach ($data as $v) {
$_param .= $this->detect_type($v);
}
$_param .= "', ";
foreach ($data as $k=>$v) {
if($v == end($data)) {
$_param .="$$k";
continue;
}
$_param .= "$$k, ";
}
return $_param;
}
public function insert( $table, $data ){
$table = $this->_prefix . $table;
$table = $this->escape($table);
$query = $this->gen_query('ins', $data, $table);
$stmt = $this->_mysqli->prepare($query);
foreach ($data as $key => $value) {
$$key = $value;
}
$test = $this->gen_param($data);
if(!$stmt->bind_param($test)) {
echo $this->_mysqli->error;
}
if($stmt->execute()){
print 'Success!'.'<br />';
} else {
die('Error : ('. $this->_mysqli->errno .') '. $this->_mysqli->error);
}
}
So when user inputs
$data = [ 'first_name' => 'foo', 'last_name' => 'bar', 'another_field' => 'blah'];
$db->insert('t1', $data);
I get this error:
Warning: Wrong parameter count for mysqli_stmt::bind_param() in path\to\class-db.php on line 138
This is line 138: if(!$stmt->bind_param($test))
Not sure why the question was downvoted. Anyways, I got this fixed by referring: this repo.
We'll need to get rid of gen_param, use an array instead and call using a callback function to get the parameter values right and refer the values.
The code is now:
public function insert( $table, $data ){
$table = $this->_prefix . $table;
$table = $this->escape($table);
$this->esc_sql_arr($data);
$query = $this->gen_query('ins', $data, $table);
$stmt = $this->_mysqli->prepare($query);
$this->_bind_param($data);
call_user_func_array(array($stmt, 'bind_param'),$this->return_ref($this->bind_arr));
if($stmt->execute()){
echo "Success!";
}else{
die('Error : ('. $this->_mysqli->errno .') '. $this->_mysqli->error);
}
$this->reset();
}
private function _bind_param($data){
foreach($data as $key=>$value) {
$this->_bind_values($value);
}
}
private function _bind_values($value) {
$this->bind_arr[0] .= $this->detect_type($value);
array_push($this->bind_arr, $value);
}
protected function return_ref(array &$arr)
{
//Reference in the function arguments are required for HHVM to work
//https://github.com/facebook/hhvm/issues/5155
//Referenced data array is required by mysqli since PHP 5.3+
if (strnatcmp(phpversion(), '5.3') >= 0) {
$refs = array();
foreach ($arr as $key => $value) {
$refs[$key] = & $arr[$key];
}
return $refs;
}
return $arr;
}
The code is nowhere near complete, but this got me started.

multiple calls to $stmt->bind_param

I'm in a situation where I want to build a code which gets $bindParam variable in this format:
$bindParams = [$type1 => $param1, $type2 => $param2, ... ]
I wanna build some code that dynamically adds that parameters to the prepared statement.
This is the code which I built so far :
$mysql = new mysqli("localhost", "root", "", "db1");
$stmt = $mysql->prepare($sql);
foreach($bindParams as $type => $data) {
$stmt->bind_param($type, $data);
}
$stmt->execute();
$result = $stmt->get_result();
// and after perhaps twiddling with the result set, but this is not the case .....
For your instance
$sql = "INSERT INTO table1 (name, age) VALUES (?,?);"
and
$bindParams = ["s" => "hello", "i" => 15]
This does not always have this structure and it can change to for example $bindParams = ["s" => "hello", "i" => 15, "d" => 22.5] and so the $sql changes respectively.
After the first time the compiler heads to $stmt->bind_param($type, $data); firefox flushes this error:
Warning: mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement in D:\PHP\tr.php on line 23
I know PDO support that as stated here at the end of the page. but perhaps as you might expect Im not a fan of PDO so ;)
My other option is to use the eval() workarounds available in php but thats out of what I might think of.
Is there another way to do this?
I had the same problem, and found an answer much simplier:
$array_of_values = array( "Brasil", "Argentina" );
$types = "ss";
$mysqli_stmt->bind_param( $types, ...$array_of_values );
This is called "argument unpacking", and is available since PHP 5.6
http://php.net/manual/pt_BR/migration56.new-features.php
Sadly mysqli doesn't support this. Calling the function over and over again overwrites the values, so you're only binding one param when you clearly have more.
There's a couple of ways to get around this
Switch to PDO. You can make one bind per function call with that
Bind the params as one aggregate using call_user_func_array
$sqltype = '';
$sqldata = [];
foreach($bindParams as $type => $data) {
$sqltype .= $type;
$sqldata[] = &$data; // MUST be a reference
}
array_unshift($sqldata, $sqltype); // prepend the types
call_user_func_array([$stmt, 'bind_param'], $sqldata);
I use something like this to do dynamic procedure calls.
Example Call:
$mapi = new MySQLIAPI($con);
$mapi->BeginProc();
$mapi->AddParameter("user", $usern, "s");
$mapi->AddParameter("email", $email, "s");
$mapi->AddParameter("passwd", $pwd, "s");
$id = $mapi->CallProc("ij_create_user");
$id = $id[0];
if(isset($id['mysql_error']) || isset($id["error"])){
return "error";
}
return $id["id"];
Example Class:
class MySQLIAPI
{
private $con = null;
private $Variables = null;
private $values = null;
private $types = null;
private $vQu = null;
private $stmt = null;
function __construct($dbc)
{
$this->con = $dbc;
$this->Variables = [];
$this->values = [];
$this->types = [];
$this->vQu = [];
}
function BeginProc()
{
$this->stmt = $this->con->stmt_init(); // initialize statement
}
function AddParameter($key, $val, $type)
{
$this->Variables[] = "#" . $key;
$this->values[] = $val;
$this->types[] = $type;
$this->vQu[] = "?";
}
//KeyPair is v = the value, t = the type s or d
function CallProc($Proc) {
$out_var = null;
$call = "";
if(sizeof($this->values) > 0)
$call = "CALL ".$Proc."(".implode(",", (array)$this->vQu).")";
else
$call = "CALL ".$Proc."()";
if($this->stmt->prepare($call));//call stored procedure with database server session variable
{
if(sizeof($this->values) > 0) {
$params = array_merge(array(implode("", $this->types)), $this->values);
call_user_func_array(array($this->stmt, 'bind_param'), $this->refValues($params));
}
$this->stmt->execute();
$result = $this->stmt->get_result();
/* Error Checking */
$mySQLiError = mysqli_stmt_error($this->stmt);
if ($mySQLiError != "") {
$this->resetStmt();
$this->stmt->close();
$this->stmt = null;
return array('mysql_error' => $mySQLiError);
}
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
$out_var[] = $row;
}
$result->free();
while($this->stmt->more_results())
{
$this->stmt->next_result();
}
$this->resetStmt();
$this->stmt->close();
$this->stmt = null;
}
return $out_var;
}
private 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;
}
private function resetStmt()
{
//Reset Params
$this->Variables = array();
$this->values = array();
$this->types = array();
$this->vQu = array();
}
}

How to insert an array into a Mysql table

I want to insert an array ($array) into a Mysql table (notification), I tried this but nothing is entering. How do I solve this?
$select = "SELECT * FROM addclique WHERE adder_id = :session_id";
$param1 = array ('session_id' => $_SESSION['id']);
$cliques = $db->query($select, $param1);
foreach($cliques as $key)
{
$array[] = $key['clique_id'];
}
$array[] = $key['clique_id'];
$notijfy = new Notification();
$notijfy->addCircle($array);
function addCircle($id_involve){
$escaped_values = array_map('mysql_real_escape_string', array_values($array));
$sql2 = "INSERT INTO notification(id_involve) VALUES (:id_involve)";
$param2 = array ('id_involve' => implode(", ", $escaped_values));
$result2 = $this->db->query ($sql2, $param2);
}
There are several ways.
I would just do something simple like this:
foreach($cliques as $key){
$array[] = $key['clique_id'];
}
$notijfy = new Notification();
$notijfy->addCircle($array);
function addCircle($array){
$insert_string = '';
$count = 0;
foreach ($array as $k => $v){
$count++;
${$k} = mysqli_real_escape_string($this->db, $v);
$insert_string .= "(" . ${$k} . ")";
if ($count < sizeof($array)){
$insert_string .= ",";
}
}
$sql2 = "INSERT INTO notification(id_involve) VALUES $insert_string;";
$result2= $this->db->query ($sql2, $param2);
}
Your major error is that you are trying to pass ($passing an array when calling the function, but in the function itself your argument is listed as $id_involve, when you obviously need an $array variable that you are using in the function itself. I also can't understand why are you trying to add an element to the $array variable outside of foreach loop in the beginning. Doesn't make any sense.
Instead of $this->db just use your connection variable.

Prepared statements with an array [duplicate]

This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Closed 1 year ago.
I have a function to do a simple insert, but am trying to make the method more robust by passing an array.
And this is the array I pass into it:
$form_data = array(
"sort_order"=>$_POST['sort_order'],
"name"=>$_POST['page_name'],
"text"=>$_POST['page_text'],
"image"=>$_POST['page_image'],
"meta_desc"=>$_POST['meta_desc'],
"meta_kw"=>$_POST['meta_kw'],
"meta_author"=>$_POST['meta_author'],
"image_thumb"=>"NULL",
);
Here is the function code:
public function insert_data($array){
$keys = array();
$values = array();
foreach($array as $k => $v){
$keys[] = $k;
if(!empty($v)){
$values[] = $v;
} else {
$values[] = "NULL";
}
}
$stmt = self::$mysqli->stmt_init();
$query = "INSERT INTO `".DB_TABLE_PAGES."` (".implode(",",$keys).") VALUES (?,?,?,?,?,?,?,?)";
$stmt->prepare($query);
$stmt->bind_param('ssssssss',implode(",",$values));
//$stmt->execute();
}
But I get this error:
Number of elements in type definition string doesn't match number of bind variables.
I know what the problem is, but I don't understand how I can achieve it.
Try this:
public function insert_data($array){
$placeholders = array_fill(0, count($array), '?');
$keys = $values = array();
foreach($array as $k => $v) {
$keys[] = $k;
$values[] = !empty($v) ? $v : null;
}
$stmt = self::$mysqli->stmt_init();
$query = 'INSERT INTO `'.DB_TABLE_PAGES.'` '.
'('.implode(',', $keys).') VALUES '.
'('.implode(',', $placeholders).')';
$stmt->prepare($query);
call_user_func_array(
array($stmt, 'bind_param'),
array_merge(
array(str_repeat('s', count($values))),
$values
)
);
$stmt->execute();
}
Or better yet, use PDO instead:
public function insert_data($array){
$placeholders = array_fill(0, count($array), '?');
$keys = $values = array();
foreach($array as $k => $v){
$keys[] = $k;
$values[] = !empty($v) ? $v : null;
}
// assuming the PDO instance is $pdo
$query = 'INSERT INTO `'.DB_TABLE_PAGES.'` '.
'('.implode(',', $keys).') VALUES '.
'('.implode(',', $placeholders).')';
$stmt = $pdo->prepare($query);
$stmt->execute($values);
}
Note: I've used the null constant because the "NULL" string will be escaped as a string (not as a null value).
I found something a little more concise.
Disclaimer, this works only since PHP 5.6 using the unpacking (splat) operator:
public function genericQueryWithParams($query, $params, $types)
{
$sql = $this->db->prepare($query));
$sql->bind_param($types, ...$params);
$sql->execute();
return $sql->get_result();
}
Instead of bind_param (which in my mind is confusing at all times), just do:
$stmt->execute($values);
You can also get rid of your loop by using array_keys() and array_values()

Use one bind_param() with variable number of input vars

I try to use variable binding like this:
$stmt = $mysqli->prepare("UPDATE mytable SET myvar1=?, myvar2=... WHERE id = ?")) {
$stmt->bind_param("ss...", $_POST['myvar1'], $_POST['myvar2']...);
but some of the $_POST['...'] might be empty so I don't want to update them in the DB.
It's not practical to take into account all the different combination of empty $_POST['...'] and although I can build the string " UPDATE mytable SET..." to my needs, bind_param() is a different beast.
I could try building its call as a string and use eval() on it but it doesn't feel right :(
You could use the call_user_func_array function to call the bind_param method with a variable number or arguments:
$paramNames = array('myvar1', 'myvar2', /* ... */);
$params = array();
foreach ($paramNames as $name) {
if (isset($_POST[$name]) && $_POST[$name] != '') {
$params[$name] = $_POST[$name];
}
}
if (count($params)) {
$query = 'UPDATE mytable SET ';
foreach ($params as $name => $val) {
$query .= $name.'=?,';
}
$query = substr($query, 0, -1);
$query .= 'WHERE id = ?';
$stmt = $mysqli->prepare($query);
$params = array_merge(array(str_repeat('s', count($params))), array_values($params));
call_user_func_array(array(&$stmt, 'bind_param'), $params);
}
This is what I use to do mysqli prepared statements with a variable amount of params. It's part of a class I wrote. It propably is overkill for what you need but it should show you the right direction.
public function __construct($con, $query){
$this->con = $con;
$this->query = $query;
parent::__construct($con, $query);
//We check for errors:
if($this->con->error) throw new Exception($this->con->error);
}
protected static $allowed = array('d', 'i', 's', 'b'); //allowed types
protected static function mysqliContentType($value) {
if(is_string($value)) $type = 's';
elseif(is_float($value)) $type = 'd';
elseif(is_int($value)) $type = 'i';
else throw new Exception("type of '$value' is not string, int or float");
return $type;
}
//This function checks if a given string is an allowed mysqli content type for prepared statement (s, d, b, or i)
protected static function mysqliAllowedContentType($s){
return in_array($s, self::$allowed);
}
public function feed($params){
//These should all be empty in case this gets used multiple times
$this->paramArgs = array();
$this->typestring = '';
$this->params = $params;
$this->paramArgs[0] = '';
$i = 0;
foreach($this->params as $value){
//We check the type:
if(is_array($value)){
$temp = array_keys($value);
$type = $temp[0];
$this->params[$i] = $value[$type];
if(!self::mysqliAllowedContentType($type)){
$type = self::mysqliContentType($value[$type]);
}
}
else{
$type = self::mysqliContentType($value);
}
$this->typestring .= $type;
//We build the array of values we pass to the bind_params function
//We add a refrence to the value of the array to the array we will pass to the call_user_func_array function. Thus say we have the following
//$this->params array:
//$this->params[0] = 'foo';
//$this->params[1] = 4;
//$this->paramArgs will become:
//$this->paramArgs[0] = 'si'; //Typestring
//$this->paramArgs[1] = &$this->params[0];
//$this->paramArgs[2] = &$this->params[1].
//Thus using call_user_func_array will call $this->bind_param() (which is inherented from the mysqli_stmt class) like this:
//$this->bind_param( 'si', &$this->params[0], &$this->params[1] );
$this->paramArgs[] = &$this->params[$i];
$i++;
}
unset($i);
$this->paramArgs[0] = $this->typestring;
return call_user_func_array(array(&$this, 'bind_param'), $this->paramArgs);
}
You use it like this:
$prep = new theClassAboveHere( $mysqli, $query );
$prep->feed( array('string', 1, array('b', 'BLOB DATA') );
The class should extend the mysqli_stmt class.
I hope this helps you in the right direction.
If you wan't I could also post the whole class, it includes variable results binding.
It is marginally more clear to build your statement using an array:
$params = array();
$fragments = array();
foreach($_POST as $col => $val)
{
$fragments[] = "{$col} = ?";
$params[] = $val;
}
$sql = sprintf("UPDATE sometable SET %s", implode(", ", $fragments));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($params);
array_insert does not exist, i'm guessing he refers to some home made function, but i'm not sure exactly what it does ... inserts the parameter types onto the array somewhere in the beginning i would guess since the value 0 is passed but hey it could be in the end too ;)
Build it as a string, but put your values into an array and pass that to bindd_param. (and substitute ?'s for values in your SQL string.
$stmt = $mysqli->prepare("UPDATE mytable SET myvar1=?, myvar2=... WHERE id = ?")) {
$stmt->bind_param("ss...", $_POST['myvar1'], $_POST['myvar2']...);
For example:
$args = array();
$sql = "UPDATE sometable SET ";
$sep = "";
$paramtypes = "";
foreach($_POST as $key => $val) {
$sql .= $sep.$key." = '?'";
$paramtypes .= "s"; // you'll need to map these based on name
array_push($args, $val);
$sep = ",";
}
$sql .= " WHERE id = ?";
array_push($args, $id);
array_insert($args, $paramtypes, 0);
$stmt = $mysqli->prepare($sql);
call_user_func_array(array(&$stmt, 'bindparams'), $array_of_params);
$stmt->bind_param($args);

Categories