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
Related
I want a function that prepare statement only once. ( Not every time whenever function called for the same Query ).
Because One of Advantage of Prepared Statement is: preparation on the query is done only once (although the statement is executed multiple times)
So whenever I call this Function it will Prepare Statement every time and it's not proper because we will miss taking one of the biggest advantage of Prepared Statement. So that, I put the Condition if( !empty($stmtName) and !isset($GLOBALS[$stmtName]) ) that Check Statement is Already set or not. ( If not then and only then Statement will prepare ) using this we will cover that advantage. But, It generates Binding Parameter failed ERROR.
My Function is here...
function qryInsert( $stmtName, $table, $field, $params, $formats )
{
$query = " INSERT INTO ".$table
." ".( (isset($field) and !empty($field)) ? " ( ".(implode(", ",$field))." ) " : " " ). " "
." VALUES( ". implode(", ", array_map(function($val) { return "?"; }, $field))." ) ";
/*if(!isset($con) or empty($con))
{
$con = $this->connection();
}*/
$a_params = array();
$a_params = array();
$param_type = '';
$n = count($formats);
for($i = 0; $i < $n; $i++)
{
$param_type .= $formats[$i];
}
$a_params[] = & $param_type;
for($i = 0; $i < $n; $i++)
{
$a_params[] = & $params[$i];
}
if( !empty($stmtName) and !isset($GLOBALS[$stmtName]) )
{
$GLOBALS[$stmtName] = $GLOBALS['con']->prepare($query);
// $stmt = $con->prepare($query);
}
if(!($GLOBALS[$stmtName]))
{
echo " Prepare failed: (" . $con->errno . ") " . $con->error; // . " <br> Query : <span style='color:tomato;'> ".$query." </span>"
}
else
{
if(!(call_user_func_array(array($GLOBALS[$stmtName], 'bind_param'), $a_params)))
{
echo "Binding parameters failed: (" . $GLOBALS[$stmtName]->errno . ") " . $GLOBALS[$stmtName]->error;
}
else
{
if(!($GLOBALS[$stmtName]->execute()))
{
echo "Execute failed: (" . $GLOBALS[$stmtName]->errno . ") " . $GLOBALS[$stmtName]->error;
}
else
{
if($meta = $GLOBALS[$stmtName]->result_metadata())
{
while ($field = $meta->fetch_field())
{
$columns[] = &$row[$field->name];
}
if(call_user_func_array(array($GLOBALS[$stmtName], 'bind_result'), $columns))
{
while ($GLOBALS[$stmtName]->fetch())
{
foreach($row as $key => $val)
{
$x[$key] = $val;
}
$results[] = $x;
}
}
else
{
echo " Error occur while Bindig Result...";
}
}
}
}
}
$GLOBALS[$stmtName]->close();
return $results;
}
INPUT:
qryInsert("insStud", "student_master", array("roll_no","name"), array(21,"Mahetab"), array("i","s"));
qryInsert("insStud", "student_master", array("roll_no","name"), array(8,"Sahil"), array("i","s"));
qryInsert("insStud", "student_master", array("roll_no","name"), array(10,"Mahammad"), array("i","s"));
OUTPUT:
First time Record will Insert...
After that raised Binding Parameter failed error...
Without this Condition if( !empty($stmtName) and !isset($GLOBALS[$stmtName]) )
My Code is Work Fine... It havn't any issue... Because It will prepare statement everytime
I used $GLOBALS variable so that whenever function called it uses same GLOBALS variable Otherwise function Perform operation with their private variable which doesn't work properly
Problem Solved...
Just wants to remove $GLOBALS[$stmtName]->close(); statement...
Because It closed statement every time function called...
So, that Binding Parameters failed...
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();
public function add($table, $data, $exclude = array()){
$fields = $values = array();
if( !is_array($exclude) ) $exclude = array($exclude);
foreach( array_keys($data) as $key ) {
if( !in_array($key, $exclude) ) {
$fields[] = "'$key'";
$values[] = "'" . $this->db->real_escape_string($data[$key]) . "'";
}
}
$fields = implode(",", $fields);
$values = implode(",", $values);
$query = "INSERT INTO $table($fields) VALUES ($values)";
if(!$result = $this->db->query($query)) {
echo "Prepare failed: (" . $this->db->errno . ") " . $this->db->error;
}
}
Error
Prepare failed: (1064) You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''name','address','country','type','status') VALUES ('Starbucks','Washington ','U' at line 1
$food->add("food", $_POST, "add");
Tested printing fields and values and they were correct. So called the function add and then got the error on syntax, but couldn't figure where query went wrong.
Key should be wrapped in ` instead of '
$fields[] = "`$key`";
I am developing a CMS with OOP PHP. It all was working well, but yesterday after adding a new feature it just crash. Now it retrieve this error when accessing any page:
Warning: mysqli::mysqli(): (HY000/1040): Too many connections in
/var/www/html/Projects/CMS4/lib/DB.php on line 15 Connection failed:
1040 Too many connections Fatal error: Call to a member function
render() on a non-object in
/var/www/html/Projects/CMS4/controllers/BaseController.php on line 33
I can handle the fatal error. But, about the "too many connections" error, I understand that I am doing too many calls to new mysqli() and that maybe I could solve it by just changing the max_connections on mysql, but is this the appropriate? Or I am doing something wrong in my code that I should improve? How can I reduce my connections? This is my DB class:
DB.php
<?php
namespace Core;
if ( !class_exists( 'DB' ) ) {
class DB {
private $cxn = null;
public function __construct() {
$this->connect();
}
private function connect() {
if ( $this->cxn !== null ) {
return;
}
$this->cxn = new \mysqli( DB_HOST, DB_USER, DB_PASS, DB_NAME );
if ( $this->cxn->connect_error ) {
die( "Connection failed: " . $this->cxn->connect_errno . ' ' . $this->cxn->connect_error );
}
}
private function create_db( $name ) {
$sql = "CREATE DATABASE IF NOT EXISTS $name";
if( !$this->cxn->query( $sql ) ){
die( "Error creating database: " . $this->cxn->errno . ' ' . $this->cxn->error );
}
}
private function select_db( $name ) {
if( !$this->cxn->select_db( $name ) ){
die( "Error selecting database: " . $this->cxn->errno . ' ' . $this->cxn->error );
}
}
/**
* Create a new table in the db with the specified columns
* #param array $tables
*/
public function create_tables( $tables ) {
$sql = '';
if ( !is_array( $tables ) ) $tables = array();
foreach ( $tables as $name => $columns ) {
$sql .= "CREATE TABLE IF NOT EXISTS $name ($columns);";
}
if( !$this->cxn->multi_query( $sql ) ){
die( "Error creating table: " . $this->cxn->errno .' '. $this->cxn->error );
}
}
/**
* Insert a row in table
* #param string $table
* #param array $data
* #param array $format
* #return boolean
*/
public function insert( $table, $data, $format ) {
if ( empty($table) || empty($data) ) {
return false;
}
//cast $data and $format to array
$data = (array) $data;
$format = (array) $format;
//Build format string
$format = $this->build_format($format);
//prepare data
list($fields, $placeholders, $values) = $this->prep_query($data);
//prepend $format in to $values
array_unshift($values, $format);
//prepare statements
if ( !( $stmt = $this->cxn->prepare("INSERT INTO {$table} ({$fields}) VALUES ({$placeholders})") ) ) {
echo "Error preparating the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//Dinamically binding
if ( !call_user_func_array(array($stmt, 'bind_param'), $this->ref_values($values)) ) {
echo "Error binding parameters: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute the query
if (!$stmt->execute()) {
echo "Error executing the insert query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//Check for succesful insertion
if ( $stmt->affected_rows ) {
return $stmt->insert_id;
}
return false;
}
/**
* Update a row in a table
* #param string $table
* #param array $data
* #param string $format
* #param array $where
* #param string $where_format
* #return boolean
*/
public function update( $table, $data, $format, $where, $where_format ) {
if ( empty($table) || empty($data) ) {
return false;
}
//cast to array
$data = (array) $data;
$format = (array) $format;
$where_format = (array) $where_format;
//Build format string
$format = $this->build_format($format);
$where_format = $this->build_format($where_format);
$format .= $where_format;
//prepare data
list($fields, $placeholders, $values) = $this->prep_query($data, 'update');
list($where_clause, $where_values) = $this->prep_where($where);
//prepend $format onto $values
array_unshift($values, $format);
$values = array_merge($values, $where_values);
//prepare statements
if ( !( $stmt = $this->cxn->prepare("UPDATE {$table} SET {$placeholders} WHERE ({$where_clause})") ) ) {
echo "Error preparating the update query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//bind params
if ( !call_user_func_array(array($stmt, 'bind_param'), $this->ref_values($values)) ) {
echo "Error binding parameters: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute
if (!$stmt->execute()) {
echo "Error executing the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//Check for succesful insertion
if ( $stmt->affected_rows ) {
return true;
}
return false;
}
/**
* Delete a row from a table
* #param string $table
* #param string|array $where
* #param string|array $where_format
* #return false
*/
public function delete( $table, $where = '', $where_format = '' ) {
if ( !is_array( $where ) ) {
$where = array( 'ID' => $where );
$where_format = 'i';
}
$where_format = (array) $where_format;
$where_format = $this->build_format($where_format);
//prepare data
list($where_clause, $where_values) = $this->prep_where($where);
//prepend $format onto $values
$values = $where_values;
array_unshift($values, $where_format);
//prepare statements
if ( !( $stmt = $this->cxn->prepare("DELETE FROM {$table} WHERE {$where_clause}") ) ) {
echo "Error preparating the delete query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//bind params
if ( !call_user_func_array(array($stmt, 'bind_param'), $this->ref_values($values)) ) {
echo "Error binding parameters: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute
if (!$stmt->execute()) {
echo "Error executing the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//Check for succesful insertion
if ( $stmt->affected_rows ) {
return true;
}
return false;
}
/**
* Select a row from a table
* #param string $table
* #param string $where
* #param string $where_format
* #return array
*/
public function select( $table, $where = '', $where_format = '' ) {
if ( !is_array( $where ) ) {
$where = array( 'ID' => $where );
$where_format = 'i';
}
$where_format = (array) $where_format;
$where_format = $this->build_format($where_format);
//prepare data
list($where_clause, $where_values) = $this->prep_where($where);
//prepend $format onto $values
$values = $where_values;
array_unshift($values, $where_format);
//prepare statements
if ( !( $stmt = $this->cxn->prepare("SELECT * FROM {$table} WHERE {$where_clause}") ) ) {
echo "Error preparating the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//bind params
if ( !call_user_func_array(array($stmt, 'bind_param'), $this->ref_values($values)) ) {
echo "Error binding parameters: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute
if (!$stmt->execute()) {
echo "Error executing the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
$results = $this->get_results($stmt);
if ( $results ) {
return $results;
} else {
throw new \Exception('Invalid query, no results founds.');
}
}
/**
* Select multiple row from a table
* #param string $table
* #param string $where
* #param string $where_format
* #return array
*/
public function select_array( $table, $where = '', $where_format = '' ) {
if ( !is_array( $where ) ) {
$where = array( 'ID' => $where );
$where_format = 'i';
}
$where_format = (array) $where_format;
$where_format = $this->build_format($where_format);
//prepare data
list($where_clause, $where_values) = $this->prep_where($where);
//prepend $format onto $values
$values = $where_values;
array_unshift($values, $where_format);
//prepare statements
if ( !( $stmt = $this->cxn->prepare("SELECT * FROM {$table} WHERE {$where_clause}") ) ) {
echo "Error preparating the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//bind params
if ( !call_user_func_array(array($stmt, 'bind_param'), $this->ref_values($values)) ) {
echo "Error binding parameters: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute
if (!$stmt->execute()) {
echo "Error executing the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
$results = $this->get_results($stmt, 'array');
if ( $results ) {
return $results;
} else {
throw new \Exception('Invalid query, no results founds.');
}
}
/**
* Select all the rows from a table
* #param string $table
* #return array
*/
public function select_all( $table ) {
//prepare statements
if ( !( $stmt = $this->cxn->prepare("SELECT * FROM {$table}") ) ) {
echo "Error preparating the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
//execute
if (!$stmt->execute()) {
echo "Error executing the query: (" . $this->cxn->errno . ") " . $this->cxn->error;
}
$results = $this->get_results($stmt, 'array');
if ( $results ) {
return $results;
} else {
throw new \Exception('Invalid query, no results founds.');
}
}
/**
* Get results from a query
* #param object $stmt
* #param string $type
* #return array
*/
private function get_results($stmt, $type = 'string') {
$stmt->store_result();
$meta = $stmt->result_metadata();
while ( $field = $meta->fetch_field() ) {
$params[] = &$row[$field->name];
}
call_user_func_array( array( $stmt, 'bind_result' ), $params );
$results = array();
while ( $stmt->fetch() ) {
foreach( $row as $key => $val ) {
$c[$key] = $val;
}
if ($type === 'array') {
$results[] = $c;
} else {
$results = $c;
}
}
if ( !empty( $results) ) return $results;
return false;
}
/**
* Build the format string for the query values
* #param array $format
* #return string
*/
private function build_format( $format ) {
$format = implode('', $format);
$format = str_replace('%', '', $format);
return $format;
}
/**
* Prepare data for a query
* #param array $data
* #param string $type
* #return array
*/
private function prep_query($data, $type = 'insert') {
//instantiate $fields and $placeholders for looping
$fields = '';
$placeholders = '';
$values = array();
//loop through $data and build $fields, $placeholders and $values
foreach ( $data as $field => $value ) {
$fields .= "{$field},";
$values[] = $value;
if ( $type == 'update' ) {
$placeholders .= $field . '=?,';
} else {
$placeholders .= '?,';
}
}
//normalize $fields and $placeholder for inserting
$fields = substr( $fields, 0, -1 );
$placeholders = substr( $placeholders, 0, -1 );
return array( $fields, $placeholders, $values );
}
/**
* Prepare where data for a query
* #param array $where
* #return array
*/
private function prep_where($where) {
$where_clause = '';
$where_values = array();
$count = 0;
foreach ($where as $field => $value) {
if ( $count > 0 ) {
$where_clause .= ' AND ';
}
$where_clause .= $field . '=?';
$where_values[] = $value;
$count++;
}
return array($where_clause, $where_values);
}
/**
* Create references for query values
* #param array $array
* #return array
*/
private function ref_values( $array ) {
$refs = array();
foreach ( $array as $key => $value ) {
$refs[$key] = &$array[$key];
}
return $refs;
}
/**
* Hash a password
* #param string $password
* #param string $nonce
* #return string
*/
public function hash_password($password, $nonce) {
$secureHash = hash_hmac('sha512', $password . $nonce, SITE_KEY);
return $secureHash;
}
/**
* Close the connection to database
*/
private function close() {
if ( !$this->cxn->close() ) {
die('Can\'t close the connection');
}
}
}
}
?>
Then I use these methods in my model classes, like in this example of the Page class:
Models/Page.php
public function insert( $data ) {
//prepare data
list($data, $cats, $tags) = $this->prep_data($data);
//insert the post
$post = $this->db->insert( $this->table, $data, 'sssss' );
//insert the post-cats relations
if ( isset( $cats ) ) {
foreach ( $cats as $cat ) {
$relation = array(
'post_id' => $post,
'term_id' => $cat,
);
$this->db->insert( 'term_relationships', $relation, 'ii' );
}
}
//insert the post-tags relations
if ( isset( $tags ) ) {
foreach ( $tags as $tag ) {
$relation = array(
'post_id' => $post,
'term_id' => $tag,
);
$this->db->insert( 'term_relationships', $relation, 'ii' );
}
}
return $post;
}
Here is the whole code: Github
It looks like you are using constants to connect to the DB - which means the class will only support connections with one set of parameters.
You could make the connection static and then you will only get one per script no matter how many times you instantiate the class. The PHP manual provides a good explanation of static usage.
This won't scale if you require a second connection to a different DB, but by then you'll be needing to re-factor the class a little anyway and shift the other constants.
if ( !class_exists( 'DB' ) ) {
class DB {
// make $cxn a static property shared between instances of this class
private static $cxn = null;
public function __construct() {
$this->connect();
}
private function connect() {
if(!is_null(self::$cxn)) {
// a connection has already been tried, and succeeded
return;
}
self::$cxn = new \mysqli( DB_HOST, DB_USER, DB_PASS, DB_NAME );
if ( self::$cxn->connect_error ) {
die( "Connection failed: " . self::$cxn->connect_errno . ' ' . self::$cxn->connect_error );
}
}
You can use this function for close connections
function __destruct()
{
//destroy open connection
}
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))