I recently started using MySQLi prepared statements. I did not liked how many rows of code that was needed for just a simple select statement. So I created a wrapper function, see the code below the questions below.
Note: get_results() or PDO is not an option for me.
My questions are:
Will it slow down the performance noticeably?
Will it be more memory intensive because of the use of the result array?
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Do I need to close or free anything else (except from closing the db connection)?
Do you see any other problems with the function or could it be improved?
Code:
class DatabaseHelper{
static function select($con, $query, $formats, $params){
$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];
}
$stmt = $con->prepare($query);
call_user_func_array(array($stmt, 'bind_param'), $a_params);
$stmt->execute();
$meta = $stmt->result_metadata();
while ($field = $meta->fetch_field()) {
$columns[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $columns);
while ($stmt->fetch()) {
foreach($row as $key => $val) {
$x[$key] = $val;
}
$results[] = $x;
}
$stmt->close();
return $results;
}
}
Used like this for example:
$users = DatabaseHelper::select($conn, "SELECT name,username FROM users WHERE id > ?", "i", array(30));
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
Will it slow down the performance noticeably?
No.
Will it be more memory intensive because of the use of the result array?
No, as long as you are selecting sensible amounts of data. In a modern application you have to select all the data first anyway, as the business logic should be separated from display logic.
Will the $stmt->close() before the return cause any problems? For example maybe the result array data is also are freed from memory?
Why not to try and see?
Do I need to close or free anything else (except from closing the db connection)?
You rather don't have to close a statement either.
Do you see any other problems with the function or could it be improved?
First and foremost. As it's a class you are writing, and not a function, there is absolutely no point in passing the connection through a parameter. Make it a static property.
Also, I would suggest to make types the last parameter with default value. In most cases you don't have to nitpick with types - a default string will do.
Besides, as your PHP version is 5.6 you can use the splat operator just to reduce the amount of code. You can check this answer of mine for the details
I would also suggest to split your function into several methods - one to execute the query and others to get the results. It will let you to re-use the same code for all kinds of queries
make sure you are watching for mysqli errors as explained here
So, ideally you'd call your query this way
$users = DatabaseHelper::getAll("SELECT name,username FROM users WHERE id > ?", [30]);
foreach ($users as $row){
echo $row['username'] . " ". $row['name'] . "<br />";
}
where getAll() method is using query() method internally to perform a query and then to fetch all the results. Similarly you may want to write getRow() and getOne() methods
Related
I'm currently struggling to solve a small problem with MySQLi's prepared statements. I'm trying to write a custom PHP function that takes a few variables and uses them to write some data into a table in my database, via prepared statement.
The issue is that I need the function to accept any number of parameters.
An example of the function in action:
db_insert_secure("Goals", "(Name, Description, Type, ProjectID)", $data);
This is supposed to write all of the info stored in the $data array into the (4) specified rows in the Goals table.
However, because the amount of parameters can change, I can't think of a way to bind them in an efficient manner.
The one idea I had was to use a switch statement that would handle binding different numbers of parameters, but that isn't the most eloquent or efficient method, I'm sure.
The script in its entirety:
function db_insert_secure($table, $columns, $data)
{
$link = db_connect();
$inputData = explode(",", $columns);
$query = "INSERT INTO ".$table." ".$columns." VALUES (";
for ($i = 0; $i < sizeof($inputData); $i ++)
{
$query .= "?";
if ($i != sizeof($inputData) - 1)
{
$query .= ", ";
}
}
$query .= ")";
echo $query;
$check_statement = mysqli_prepare($link, $query);
//mysqli_stmt_bind_param($check_statement, 's', $data[0]);
echo $check_statement;
db_disconnect($link);
}
NOTE: the db_connect and db_disconnect scripts are custom scripts for opening and closing a connection to the database. db_connect simply returns the connection object.
Can anyone think of a solution to this problem that does not involve using eval?
Funnily enough, after struggling with this for the past 12 hours or so, I actually managed to find a solution to the problem within a few minutes of starting this thread.
You can use an array as data for a prepared statement by placing "..." in front of it.
For example, in my case:
mysqli_stmt_bind_param($preparedStatement, 'ssss', ...$data);
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.
I am trying to write a very small abstraction layer for my mysqli connection and have run into a problem.
Since I am maintaining older code I need to get an associative array from my query as this is the way the code has been set up and therefor less work for me once this works...
This function does work with all kinds of queries(not just select)...
my function I wrote is this:
function connectDB($query,$v=array()) {
$mysqli = new mysqli(HOST,USER,PW,DATABASE);
if($res=$mysqli->prepare($query)) {
//dynamically bind all $v
if($v) {
$values=array($v[0]);
for($i=1; $i<count($v); $i++) {
${'bind'.$i}=$v[$i];
$values[]=&${'bind'.$i};
}
call_user_func_array(array($res,'bind_param'),$values);
}
$res->execute();
//bind all table rows to result
if(strtolower(substr($query,0,6))=="select") {
$fields=array();
$meta=$res->result_metadata();
while($field=$meta->fetch_field()) {
${$field->name}=null;
$fields[$field->name]=&${$field->name};
}
call_user_func_array(array($res,"bind_result"),$fields);
//return associative array
$results = array();
$i=0;
while($res->fetch()) {
$results[$i]=array();
foreach($fields as $k => $v) $results[$i][$k] = $v;
$i++;
}
}
else {
$results=$mysqli->affected_rows;
if($mysqli->affected_rows<1) $results=$mysqli->info;
}
$res->close();
}
$mysqli->close();
return $results;
}
so if I call:
$MySqlres=connectDB("select * from `modx_events` events limit 1");
var_dump($MySqlres);
I get a nice associative array with the content of my select.
Now unfortunately the following mysql query will return NULL as a value to all of it's array keys:
$MySqlres=connectDB("select *, events.`id` as `ID`,venues.`name` as `venueName`,
venues.`suburb` as `venueSuburb`,venues.`advertiser` as `venueAdvertiser`
from `modx_events` events left join `modx_venues` venues on events.`venue`=venues.`id`
where events.`id`!='e' order by events.`start_date` asc, venues.`name` limit 1");
(the same query runs as pure SQL and will return the correct values)
Any idea what this could be? Does my associative array function fail? Is there something wrong with the way I implemented the query?
ps: PDO is not an option and mysqlnd is not installed... :(
ADDED QUESTION
is this too much of overhead just to preserve the associative array return? Should I go with $res->fetch_object() instead?
Mysqli is very poor with dynamic prepared statements, which makes small abstraction layer creation a nightmare.
I strongly suggest you to switch to PDO or get rid of prepared statements and create a regular query based on the manually handled placeholders (preferred).
As a palliative patch you can try to use get_result() function which will return a regular result variable which you can traverse usual way with fetch_assoc()
But it works only with mysqlnd builds.
Also note that creating mysqli object for the every query is a big no-no.
Create it once and then assign it in your query function using global $mysqli;
is this too much of overhead
I don't understand what overhead you're talking about
I just fixed the function.
Perhaps this is interesting for someone else:
function connectDB($mysqli,$query,$v=array()) {
if($mysqli->connect_errno) {
return array('error'=>'Connect failed: '.$mysqli->connect_error); //error handling here
exit();
}
if(substr_count($query,"?")!=strlen($v[0])) {
return array('error'=>'placeholders and replacements are not equal! placeholders:'.substr_count($query,"?").' replacements:'.strlen($v[0]).' ('.$v[0].')'); //error handling here...
exit();
}
if($res=$mysqli->prepare($query)) {
//dynamically bind all $v
if($v) {
$values=array($v[0]);
for($i=1; $i<count($v); $i++) {
${'bind'.$i}=$v[$i];
$values[]=&${'bind'.$i};
}
call_user_func_array(array($res,'bind_param'),$values);
}
$res->execute();
//bind all table rows to result
if(strtolower(substr($query,0,6))=="select") {
$field=$fields=$tempRow=array();
$meta=$res->result_metadata();
while($field=$meta->fetch_field()) {
$fieldName=$field->name;
$fields[]=&$tempRow[$fieldName];
}
$meta->free_result();
call_user_func_array(array($res,"bind_result"),$fields);
//return associative array
$results=array();
$i=0;
while($res->fetch()) {
$results[$i]=array();
foreach($tempRow as $k=>$v) $results[$i][$k] = $v;
$i++;
}
$res->free_result();
}
else { //return infos about the query
$results["affectedRows"]=$mysqli->affected_rows;
$results["info"]=$mysqli->info;
$results["insertID"]=$mysqli->insert_id;
}
$res->close();
}
return $results;
}
cheers
This is similar to this question - Are Dynamic Prepared Statements Bad? (with php + mysqli), however since it is 4 years old I wanted to get a more upto date answer.
I've written a class which, although I haven't tested it on more copmlex sql queries, it has worked without fail on simple sql queries, however I'm not sure if doing so has bypassed one of the main reasons for prepared statements - security.
I have made use of the call_user_func_array which was easy enough with the bind_param statements however with the bind_result was a little trickier. I originally used get_result however the host I've gone with doesn't have mysqlnd available, but I managed to get around using the metadata. This is the full class I have written.
Do you think this is secure?
The passed in values are:
$sql is the passed in sql statement:
SELECT * FROM users WHERE id = ? AND created_timestamp > ?
$mysqli is the mysqli connection
$para is the placeholder in the prepared statement:
array ($types = 'ii', 23, 1235376000)
The class:
class crudModel {
function ps($sql, $mysqli, $para) {
//this function should work for just about any simple mysql statement
//for more complicated stuff like joins, unions etc,. we will see
if ($prep = $mysqli->prepare($sql)) {
call_user_func_array(array($prep, 'bind_param'), $this->makeValuesRef($para, $mysqli));
$prep->execute();
$meta = $prep->result_metadata();
while ($field = $meta->fetch_field()) {
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($prep, 'bind_result'), $parameters);
while ($prep->fetch()) {
foreach ($row as $key=>$val) {
$x[$key] = $val;
}
$data[] = $x;
}
return $data;
}
}
function makeValuesRef($array, $mysqli) {
$refs = array();
foreach($array as $key => $value) {
$array[$key] = $mysqli->real_escape_string($value); //i don't think escaping is necessary, but it can't hurt (??)
$refs[$key] = &$array[$key];
}
return $refs;
}
}
What you're doing here isn't a dynamic prepared statement. You're just putting some syntatic sugar on top of the MySQLi API (which sucks).
In short, there aren't really any security concerns present from the code you've shown here. In fact, this sort of practice is quite good, because it makes it easier to verify that you're doing it correctly later (since the MySQLi API sucks).
So you're fine. I would worry about the areas you're generating the queries, and ensuring that you're not accidentally putting user-data into them without white-listing...
i just need a little help on how i could implement this function. i cant quite read this. I found this function to stick the results from a mysqli prepared statement query into an array which is what i am trying to do, but i am unsure how to use the function.
$meta = $statement->result_metadata();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
call_user_func_array(array($statement, 'bind_result'), $params);
while ($statement->fetch()) {
foreach($row as $key => $val) {
$c[$key] = $val;
}
$hits[] = $c;
}
$statement->close();
while ($field = $meta->fetch_field()) {
$params[] = &$row[$field->name];
}
This looks at the result set and extracts the field names into an array.
call_user_func_array(array($statement, 'bind_result'), $params);
That array of field names is then used to auto-create variables of the same name as the field, to which the database results are bound to. This is very bad practice, as it auto-creates variables based on query results, and can potentially overwrite a variable you'd be using for other purposes elsewhere. You'd never know why, because there's no explicit assignment to that variable name to search for.
The while loop is highly inefficient, but retrieves each row from the query results and places them into an array called $hits.
So, basically, it's a gold plated piece of bovine excrement that impressively does very little.