I am trying to build a dynamic prepared statement but I am stuck at the bind_param part. I tried to read other answers referring to call_user_func_array but I couldn't figure out how to adapt it here:
//connecting
$connection = new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
//info submitted through a form
$values_columns = array(
"column_one" => "value_one", //value_one will be replaced by $_POST['value_one']
"column_two" => "value_two", //value_two will be replaced by $_POST['value_two']
"column_three" => "value_three" //value_three will be replaced by $_POST['value_three']
);
$value_type = implode('', array('s', 's', 's'));
//preparing and binding my query dinamically
function prepare_bind($table_name, $values_columns, $value_type) {
global $connection;
$columns_string = "";
$question_marks = "";
$flag = 0;
$count = count($values_columns);
foreach ($values_columns as $column => $value) {
$flag++;
// building the prepare
if ($flag == $count) {
$columns_string .= $column;
$question_marks .= "?";
} else {
$columns_string .= $column . ", ";
$question_marks .= "?, ";
}
}
$sql = $connection->prepare('INSERT INTO ' . $table_name . ' (' . $columns_string . ') VALUES (' . $question_marks . ')');
$sql->bind_param($value_type, /*I am stuck here while trying to add the values*/);
}
Is there any way to get out of this situation. It is the first time I am using this approach and I don't know if I got it right. Thank you very much.
Easiest solution to generating the placeholders:
function placeholderMarks($count) {
return join(', ', array_fill(0, $count, '?'));
}
Insert the result of this function into the query with placeholderMarks(count($values_columns))
Related
So I am trying to make an undetermined amount of bindParam calls within a foreach, but for some reason it fails. I know the $sql variable is working fine, but I am pretty sure it is failing at the bindParam. Is there any reason for this?
$sql = "INSERT INTO " . $row1["rand"] . " (" . $areas . ") VALUES (" . $vals . ")";
echo $sql;
$entry2 = $conn->prepare("'".$sql."'");
//echo "swag";
foreach($splitHeader as $element){
if(strlen($element)>0) {
$thisVal = "':" . $element . "'";
$entry2->bindParam($thisVal,$_POST[$element]);
}
}
$entry2->execute();
The number of parameters that you define in the query must match the number of parameters that you bind.
You would need to loop twice trough your data : once to dynamically construct a sql statement (that you can then prepare), and then a second time to bind the parameters, before finally calling execute.
Here is an adaptation of your code that demonstrates the principle :
$cols = "";
$vals = "";
foreach( $splitHeader as $element ) {
if( strlen($element) > 0 ) {
if ( strlen($cols) > 0 ) {
$cols .= ", ";
$vals .= ", ";
}
$cols .= $element;
$vals .= "?";
}
}
$sql = "INSERT INTO " . $row1["rand"] . " (". $cols . ") VALUES(". $vals . ")";
echo $sql;
$sth = $conn->prepare($sql);
$i = 1;
foreach($splitHeader as $element){
if( strlen($element) > 0 ) {
$sth->bindParam( $i, $_POST[$element] );
$i++;
}
}
$sth->execute();
It's far from perfect, but I'm trying to create a function to insert data into a SQL table, using MySQLI. I want to create a generic function, to insert different types of data in different databases. I have the following so far:
/**
* Add data to specified table. Data consist of column name as key, and value.
* Table is a string of the table to insert into.
* #param array $data
* #param string $table
* #return string
*/
private function insert( $data = array(), $table = null ){
foreach( $data as $key => $value ){
// Create arrays of separate keys and values
$keys[] = $key;
$values[] = $value;
// Get type of data
switch( gettype( $value ) ){
case "integer":
$types[] = "i";
break;
case "string":
$types[] = "s";
break;
default:
$types[] = "i";
break;
};
// for each variable, add a questionmark
$vars[] = "?";
}
// Create strings out of the data
$key = implode( ",", $keys );
$var = implode( ",", $vars );
$type = implode( "", $types );
$value = '"' . implode( '\", \"', $values ) . '"';
// prepare SQL statement
// var_dump( $sql ) = 'INSERT INTO table (var1,var2,var3) VALUES (?,?,?)'
$sql = "INSERT INTO " . $table . " (" . $key . ") VALUES (" . $var . ")";
// Prepare SQL insert
// $this->conn = new mysqli($this->server, $this->user, $this->pass, $this->name);
if( ! ( $stmt = $this->conn->prepare( $sql ) ) ) {
return "Preparing failed!: (" . $this->conn->errno . ") " . $this->conn->error;
}
// Bind parameters. THIS IS WHERE THE ISSUE IS!
if( ! $stmt->bind_param( $type, $values ) ) {
return "Binding failed! (" . $stmt->errno . ") " . $stmt->error;;
}
// Execute the statement
if( ! $stmt->execute() ){
return "Executing failed! (" . $stmt->errno . ") " . $stmt->error;;
}
}
The issue is at binding the parameters. I can't find a good way to bind them, as I've got multiple variables with values and keys, but they're all in array format, and bind_param requires a new variable for each
In short, I'm looking for a way to add an array of unspecified length into my SQL (in a secure way, ofcourse).
If you are using php 5.6+, you can use the ... operator to unpack an array.
In your example:
$stmt->bind_param( $type, ...$values )
See example #14 in the manual.
/* Bind parameters. Types: s = string, i = integer, d = double, b = blob */
$a_params = array();
$param_type = '';
$n = count($a_param_type);
for($i = 0; $i < $n; $i++) {
$param_type .= $a_param_type[$i];
}
/* with call_user_func_array, array params must be passed by reference */
$a_params[] = & $param_type;
for($i = 0; $i < $n; $i++) {
/* with call_user_func_array, array params must be passed by reference */
$a_params[] = & $a_bind_params[$i];
}
/* Prepare statement */
$stmt = $conn->prepare($sql);
if($stmt === false) {
trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->errno . ' ' . $conn->error, E_USER_ERROR);
}
/* use call_user_func_array, as $stmt->bind_param('s', $param); does not accept params array */
call_user_func_array(array($stmt, 'bind_param'), $a_params);
/* Execute statement */
$stmt->execute();
/* Fetch result to array */
$res = $stmt->get_result();
while($row = $res->fetch_array(MYSQLI_ASSOC)) {
array_push($a_data, $row);
}
Reference: http://www.pontikis.net/blog/dynamically-bind_param-array-mysqli
I have the following code but i am not sure how to check if insert is success. execute returns resource id. I would like to check if success and return all errors on fail.
public function persist()
{
$update = FALSE;
if(!is_array($this->tablePrimaryKey)) {
if(!empty($this->fieldVals[$this->tablePrimaryKey])) {
$update = true;
}
}
if ($update) {
$sql = "UPDATE " . $this->tableName . " SET ";
$binds = [];
foreach ($this->fieldVals as $key=>$val) {
if ($key != $this->tablePrimaryKey) {
if(in_array($key, $this->DATE_IDS)) {
$sql .= '"' . strtoupper($key) . '" = sysdate,';
} else {
$bind = 't_' . $key;
$binds[$bind] = $val;
$sql .= '"' . strtoupper($key) . '" = :' . $bind . ',';
}
}
}
$sql = substr($sql,0,-1);
$sql .= " WHERE " . $this->tablePrimaryKey . " = '" . $this->fieldVals[$this->tablePrimaryKey] ."'";
} else {
$binds = $fields = $date_fields = [];
if(!empty($this->tablePrimaryKey) && !is_array($this->tablePrimaryKey)) {
$this->fieldVals[$this->tablePrimaryKey] = $this->generateNewPrimaryKey();
}
foreach ($this->fieldVals as $key=>$val) {
$bind = ':t_' . $key;
if (in_array($key, $this->DATE_IDS)) {
$date_fields[] = strtoupper($key);
} else {
$binds[$bind] = $val;
$fields[] = strtoupper($key);
}
}
$sql = 'INSERT INTO ' . $this->tableName . '("' . implode('","', $fields);
if(count($date_fields) >0) {
$sql .= '","';
$sql .= implode('","', $date_fields);
}
$sql.='") VALUES (' . implode(',', array_keys($binds));
if(count($date_fields) >0) {
$cnt=0;
foreach($date_fields as $date) {
$cnt++;
if(preg_match('/NULL/i', $this->fieldVals[strtolower($date)], $result)) {
$sql .= ",NULL";
} elseif(isset($this->fieldVals[strtolower($date)])) {
$sql .= ",TO_DATE('" . (new DateTime($this->fieldVals[strtolower($date)]))->format("Y-M-d H:i:s") . "', 'yyyy/mm/dd hh24:mi:ss')";
} else {
$sql .= ",sysdate";
}
}
}
$sql .= ')';
}
$this->oiDb->parse($sql, $binds);
return $this->oiDb->execute();
}
I run $result = $oiRequests->hydrate($reportingRequest)->persist();. $reportingRequest is key,value pair of columns/values. $result contains resource id. $oiRequests is my model.
I have tried
$num_rows = oci_fetch_assoc ($result);
print_r($num_rows);
returns
Warning: oci_fetch_assoc(): ORA-24374: define not done before fetch or execute and fetch in /var/SP/oiadm/docroot/dev/uddins/requestportal/requestportal_ajax.php on line 65
Most of the OCI functions return false on error. This means you can do a simple check on the return value and, if it's false, call oci_error().
For the specific case of checking if an INSERT statement worked you can reference the example code for oci_commit(). The relevant part of that example is duplicated here:
// The OCI_NO_AUTO_COMMIT flag tells Oracle not to commit the INSERT immediately
// Use OCI_DEFAULT as the flag for PHP <= 5.3.1. The two flags are equivalent
$r = oci_execute($stid, OCI_NO_AUTO_COMMIT);
if (!$r) {
$e = oci_error($stid);
trigger_error(htmlentities($e['message']), E_USER_ERROR);
}
I have the following code in an application I'm building and to be honest... it feels like a great deal of pain when I want to change something like this. I've always had this problem with SQL in code but never understood how to address it. Is there some way or common practice that would make the SQL here a bit easier to maintain and change? (I've read not to use stored procedures)
$stmt_arr = array(
':steamid' => isset($playerSummary['steamid']) ? $playerSummary['steamid'] : "",
':personaname' => isset($playerSummary['personaname']) ? utf8_encode($playerSummary['personaname']) : "",
':community_vis_state' => isset($playerSummary['communityvisibilitystate']) ? $playerSummary['communityvisibilitystate'] : "",
':profile_state' => isset($playerSummary['profilestate']) ? $playerSummary['profilestate'] : "NULL",
':profile_url' => isset($playerSummary['profileurl']) ? $playerSummary['profileurl'] : "",
':avatar_url' => isset($playerSummary['avatar']) ? $playerSummary['avatar'] : "",
':avatar_medium' => isset($playerSummary['avatarmedium']) ? $playerSummary['avatarmedium'] : "",
':avatar_full' => isset($playerSummary['avatarfull']) ? $playerSummary['avatarfull'] : "",
':shallow_update' => $isFriend
);
$stmt = $this->DBH->prepare("INSERT INTO `user`(`steamid`, `personaname`, `community_visibility_state`, "
. "`profile_state`, `profile_url`, `avatar_url`, `avatar_medium_url`, `avatar_full_url`, `last_updated`, `shallow_update`)"
. " VALUES (:steamid, :personaname, :community_vis_state, :profile_state, :profile_url, "
. ":avatar_url, :avatar_medium, :avatar_full, NOW(), :shallow_update) "
. "ON DUPLICATE KEY UPDATE `steamid` = VALUES(`steamid`), "
. "`personaname` = VALUES(`personaname`), "
. "`community_visibility_state` = VALUES(`community_visibility_state`), "
. "`profile_state` = VALUES(`profile_state`), "
. "`profile_url` = VALUES(`profile_url`), "
. "`avatar_url` = VALUES(`avatar_url`), "
. "`avatar_medium_url` = VALUES(`avatar_medium_url`), "
. "`avatar_full_url` = VALUES(`avatar_full_url`), "
. "`last_updated` = VALUES(`last_updated`),"
. "`shallow_update` = VALUES(`shallow_update`)");
$stmt->execute($stmt_arr);
Use this function
function pdo_insert($con, $table, $data_arr)
{
if (!is_array($data_arr) || !count($data_arr)) return false;
$bind = ':'.implode(',:', array_keys($data_arr));
$sql = 'INSERT into '.$table.'('.implode(',', array_keys($data_arr)).') '.
'values ('.$bind.')';
$stmt = $con->prepare($sql);
$status = $stmt->execute(array_combine(explode(',',$bind), array_values($data_arr)));
if($status)
{
// success
$msg = 'Data added to database successfully';
return $msg;
}
else
{
// failure
$msg = 'Error in adding data into database';
return $msg;
}
}
Calling the function
$msg = pdo_insert($db, 'table name here', $_POST);
// see the result
echo $msg;
function generateSQL($table, $arr) {
$sql = "INSERT INTO ".$table . " (";
foreach($arr as $k => $v) {
$sql .= "`".substr_replace($k, "", 0, 1)."`, ";
}
$sql = substr_replace($sql, "", -2);
$sql .= ") VALUES (";
foreach($arr as $k => $v) {
$sql .= $k.", ";
}
$sql = substr_replace($sql, "", -2);
$sql .= ") ON DUPLICATE KEY UPDATE ";
foreach($arr as $k => $v) {
$sql .= "`".substr_replace($k, "", 0, 1)."` = VALUES(`".substr_replace($k, "", 0, 1)."`), ";
}
$sql = substr_replace($sql, "", -2);
return $sql;
}
print_r(generateSQL("user", $stmt_arr));
Where did you read not to use stored procedure ?
They are the solution to your problem, when queries get complex, it is better to move the logic to a sotored procedure and just execute it (e.g CALL sp_isnert_user ?, ?)
They also allow you to maintain/modify your SQL logic without really touching your code.
I am currently going through a nettuts.com tutorial on building a twitter clone and there they have a function for deleting rows from a database and they are using the query method but i tried converting the function to a prepared statement.However I get the Invalid parameter number: parameter was not defined error.Here is the code for the function
public function delete($table, $arr){
$query = "DELETE FROM " . $table;
$pref = "WHERE ";
foreach ($arr as $key => $value) {
$query .= $pref. $key . " = " . ":" . $key;
$pref = "AND ";
}
$query .= ";";
$result = $this->db->prepare($query);
$result->execute($arr);
}
$connect = new Model();
$connect->delete("ribbits", array("user_id" => 2,
"ribbit" => "trial ribbit"
));
can someone please tell me what I am doing wrong?Thank you!
When you pass your array to ->execute(), the keys need to have the : character before them (just like they appear in the SQL query).
So, in your delete function, build the SQL query like this:
public function delete($table, $arr){
$keys = array();
$values = array();
foreach($arr as $key => $value){
$keys[] = $key . " = :" . $key;
$values[":".$key] = $value;
}
$query = "DELETE FROM " . $table . " WHERE " . implode(" AND ", $keys);
$result = $this->db->prepare($query);
$result->execute($values);
}