I've got a database query function that works well -- except that I'm running into what's apparently a known issue with mysqli prepared statements and longtext fields. What happens is that the longtext field always comes up empty even though running the query through phpMyAdmin works fine. According to http://www.workinginboxershorts.com/php-mysqli-returns-empty-variables-from-longtext-column, switching the datatype to text solves the problem. However, in my case I'd really prefer to leave the field as longtext as I can foresee times when that extra space would be valuable.
I'm using parameterized queries, which evidently is the problem. Here's my function:
// Bind results to an array
// $stmt = sql query, $out = array to be returned
function stmt_bind_assoc (&$stmt, &$out) {
$data = mysqli_stmt_result_metadata($stmt);
$fields = array();
$out = array();
$fields[0] = $stmt;
$count = 1;
while($field = mysqli_fetch_field($data)) {
$fields[$count] = &$out[$field->name];
$count++;
}
call_user_func_array('mysqli_stmt_bind_result', $fields);
}
// DB Query
// $query = SQL query, $params = array of parameters, $rs = whether or not a resultset is expected, $newid = whether or not to retrieve the new ID value;
// $onedimensionkey = key required to convert array into simple one dimensional array
function db_query($query, $params, $rs = true, $newid = false, $onedimensionkey = false) {
$link = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_NAME);
if (!$link) {
print 'Error connecting to MySQL Server. Errorcode: ' . mysqli_connect_error();
exit;
}
// Prepare the query and split the parameters array into bound values
if ($sql_stmt = mysqli_prepare($link, $query)) {
if ($params) {
$types = '';
$new_params = array();
$params_ref = array();
// Split the params array into types string and parameters sub-array
foreach ($params as $param) {
$types .= $param['type'];
$new_params[] = $param['value'];
}
// Cycle the new parameters array to make it an array by reference
foreach ($new_params as $key => $parameter) {
$params_ref[] = &$new_params[$key];
}
call_user_func_array('mysqli_stmt_bind_param', array_merge(array($sql_stmt, $types), $params_ref));
}
}
else {
print 'Error: ' . mysqli_error($link);
exit();
}
// Execute the query
mysqli_stmt_execute($sql_stmt);
// If there are results to retrive, do so
if ($rs) {
$results = array();
$rows = array();
$row = array();
stmt_bind_assoc($sql_stmt, $results);
while (mysqli_stmt_fetch($sql_stmt)) {
foreach ($results as $key => $value) {
$row[$key] = $value;
}
$rows[] = $row;
}
if ($onedimensionkey) {
$i = 0;
foreach ($rows as $row) {
$simplearray[$i] = $row[$onedimensionkey];
$i++;
}
return $simplearray;
}
else {
return $rows;
}
}
// If there are no results but we need the new ID, return it
elseif ($newid) {
return mysqli_insert_id($link);
}
// Close objects
mysqli_stmt_close($sql_stmt);
mysqli_close($link);
}
According to the link that I posted there is a workaround involving the order in which things are done, but either I'm handling my query in a completely different manner than the example or I'm simply not understanding something important.
Thanks to anyone who can help!
EDIT: Thanks to Corina's answer, I've solved this -- for anyone else who runs into the problem, you will simply need to add the following after the mysql_stmt_execute command:
// Execute the query
mysqli_stmt_execute($sql_stmt);
// Store results
mysqli_stmt_store_result($sql_stmt);
I managed to solve the same issue by calling mysqli_stmt_store_result before binding the data.
Someone had the same problem and shared the answer on the php.net website:
Apparently, if you have longtext present, you HAVE to call
store_result before using bind_result.
http://bugs.php.net/bug.php?id=47928
Related
So I'm having an odd problem... We use parameterized queries to prevent SQL Injection in our code but I'm having trouble with some of that behavior and while I've found an ugly way around it, my work around kind of defeats the purpose of the parameterization.
Suppose I'm making this query:
$db = new csmysqli('database',$host,$user,$pass);
$value = x;
$stmt = "INSERT INTO table SET value='%s'";
$result = $db->prepare($stmt, $value);
echo $result;
Now here's the problem... if x is a string, or an int we get this for result:
INSERT INTO table SET value='123';
No problem... however, if x is null:
INSERT INTO table SET value='NULL'; <--- the single quotes there cause a problem.... Ok so I try this to get around it:
$value = "'x'"; // Notice the quotes now go around x
$stmt = "INSERT INTO table SET value=%s";
$result = $db->prepare($stmt, $value);
echo $result;
And we get this if x is an int or string:
INSERT INTO table SET value=\'x\';
And the null now works:
INSERT INTO table SET value=NULL;
So the question is:
How can I get both normal data and NULL data to correctly populate with parameterization ?
EDIT:
I should have mentioned I'm using a special mysqli_helper script:
class csmysqli extends mysqli
{
public function __construct($dbname = '', $host,$user,$pass)
{
parent::__construct($host, $user, $pass, $dbname);
}
public function query($query)
{
$numParams = func_num_args();
$params = func_get_args();
//merge in parameters only if needed
if ($numParams > 1) {
for ($i = 1; $i < $numParams; $i++) {
$params[$i] = parent::real_escape_string($params[$i]);
}
$query = call_user_func_array('sprintf', $params);
}
return parent::query($query, MYSQLI_STORE_RESULT);
}
public function prepare($query)
{
$numParams = func_num_args();
$params = func_get_args();
//merge in parameters only if needed
if ($numParams > 1) {
for ($i = 1; $i < $numParams; $i++) {
$params[$i] = parent::real_escape_string($params[$i]);
}
$query = call_user_func_array('sprintf', $params);
}
return $query;
}
}
With native mysqli parameterization there is no problem to get both normal data and NULL data to correctly populate.
With your home-brewed parameterization you have to check the parameter type and act accordingly.
I've seen this question before but all the solutions do not work for me. The main solution is to store the result which I already do. Most people say I am running 2 simultaneous queries which I don't understand. I may have used this function below more than once but I always free and close the statement so not sure why this is happening.
You can most likely ignore the top half of the function which just generates a string to represent the types. This is the full DB class I made:
(I edited it since I originally posted it and replaced self::$connection with self::$db)
class DB {
private static $dbhost = "localhost";
private static $dbuser = "some_user"; // placeholder
private static $dbpass = "some_assword"; // placeholder
private static $dbname = "database_name"; // placeholder
private static $db;
public static function connect() {
self::$db = new mysqli(self::$dbhost, self::$dbuser, self::$dbpass, self::$dbname);
if (self::$db->connect_errno) {
die("Database mysqli failed: " .
self::$db->connect_error . " (" .
self::$db->connect_errno . ")"
);
}
}
// IGNORE THIS! It just formats the parameters for the
// call_user_func_array function to work correctly.
private static function getBindings(&$params) {
$types = "";
$bindings = array();
foreach ($params as $value) {
switch (gettype($value)) {
case "integer":
$types .= "i";
break;
case "double":
$types .= "d";
break;
case "string":
$types .= "s";
break;
default:
$types .= "s";
break;
}
}
foreach($params as $key => $value)
$bindings[$key] = &$params[$key]; // assign to references (because bind_param requires references)
// to add a string of variable types to the start of the $bindings array (such as 'sss')
array_unshift($bindings, $types);
return $bindings;
}
public static function query($query, $params) {
if (!isset(self::$db)) { self::connect(); }
if (empty($params)) {
// prepared statement not needed
return self::$db->query($query);
}
$successful = false;
$bindings = self::getBindings($params);
// MySQL prepared statement execution:
$statement = self::$db->prepare($query);
call_user_func_array(array($statement, 'bind_param'), $bindings);
$statement->execute();
$statement->store_result();
if ($statement->num_rows > 0) {
// for select queries
$successful = $statement->get_result(); // does not work! (returns boolean)
echo self::$db->errno; // 2014
echo "<br />";
echo self::$db->error; // Commands out of sync; you can't run this command now
echo "<br />";
// this method works fine (but I need to return the result set!):
/*$name = false; $link = false;
$statement->bind_result($name, $link);
while ($statement->fetch()) {
echo 'name: '.$name.'<br>';
echo 'link: '.$link.'<br>';
}*/
} else if ($statement->affected_rows > 0) {
// for insert queries
$successful = true;
}
$statement->free_result();
$statement->close();
return $successful;
}
public static function close() {
if (isset(self::$db)) self::$db->close();
}
}
EDIT: This is what one of my requests looks like (I have queried 2 requests on this same page using my DB class and DB::query(...) function):
$result = DB::query("SELECT * FROM table_name;");
if ($result) {
while ($row = $result->fetch_assoc()) {
// do stuff
}
$result->close();
}
For the love of what is sane, change your driver to PDO and make all this code into
public static function query($query, $params = NULL)
{
if (!$params) {
return self::$connection->query($query);
}
$statement = self::$connection->prepare($query);
$statement->execute($params);
return $statement;
}
to be used like this
$result = DB::query("SELECT * FROM table_name");
foreach($result as $row) {
// do stuff
}
The MySQL documentation regarding Commands out of sync suggest two possibilities:
You have used a result and are trying to execute a new query before freeing the last result.
You are running two queries (not necessarily simultaneously) without using or storing the result between each one.
The logic in your code shows the only situation where you are not freeing results is when no prepared statement is required. The answer to your problem may be to deal with this particular result and free or store it.
I can see from your code self::$connection is a static reference, therefore it could be possible that any query executed in that scope is using the same connection. It is hard to tell without being able to see your full class.
I know they're many, many questions already about this, but I have no idea why mine doesn't work.
If I just use the following, my code works fine:
$this->stmt->bind_param("ii", $params[0], $params[1]);
But if I use the call_user_func_array, it breaks. One suggestion I got was passing the $parameters array by reference, but adding an & before the variable also broke my code...
Any help is greatly recieved!
Here's my code:
DB class:
function selectQuery($sql, $paramTypes = false, $params = false) {
//Prepare statement
$this->stmt = $this->conn->prepare($sql);
if($this->stmt === false) {
//We have an error
echo 'Wrong SQL: ' . $sql . ' Error: ' . $this->conn->error;
}
//This part doesn't work...
// if ($params) {
// //Bind an unknown number of parameters
// $parameters = array_merge(array($paramTypes), $params);
// call_user_func_array(array($this->stmt, 'bind_param'), $parameters);
// }
//This works.
$this->stmt->bind_param("ii", $params[0], $params[1]);
//Execute statement
$this->stmt->execute();
if ($this->stmt->error) {
echo $this->stmt->error;
return false;
}
//Get the results
$result = $this->stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
//Close statement
$this->stmt->close();
//Return the results
return $data;
}
Test page:
<?php
require_once('DatabaseAccess.php');
$db = new DB();
$sql = "SELECT * FROM table WHERE id = ? OR id = ?";
echo "Fetching data....<br>";
$result = $db->selectQuery($sql, "ii", array(1, 2));
foreach($result as $r) {
echo "<pre>".print_r($r, 1)."</pre>";
}
?>
Decided to add more information:
I'll be using this function to pass in the parameter types and parameters, but the amount will vary. When I looked up how to do this everyone suggested the call_user_func thing, but each time I try it (tried a few different ways) it won't work. Read through many threads, but it never seems to work. If I just use the bind_params function directly it works and I get the correct data returned.
Using the call_user_func thing I was getting the no data for the ? mysqli error, which is when I tried passing by reference and the code just broke completely...
Put it before call_user_func_array()
$res = array();
foreach($parameters as $key => $value) {
$res[$key] = &$parameters[$key];
}
I have created a class to handle all database operations for my application. So far I have gathered and displayed the data in foreach statements within my view. What I wish to be able to do is get and set individual elements from a function from my database class.
Here is my database function for displaying data within a foreach:
public function test($sql, $type, $param)
{
$variables = array();
$results = array();
// Mysqli
if($stmt = $this->AC->Mysqli->prepare($sql))
{
$params = array_merge($type, $param);
call_user_func_array(array($stmt, 'bind_param'), $this->make_values_referenced($params));
$stmt->execute();
$stmt->store_result();
// Existance
if($stmt->num_rows > 0)
{
$meta = $stmt->result_metadata();
while($field = $meta->fetch_field())
{
$parameters[] = &$row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while($stmt->fetch())
{
$elemet = array();
foreach($row as $key => $val)
{
$element[$key] = $val;
}
$results[] = $element;
}
return $results;
}
else
{
$results = FALSE;
return $results;
}
}
else
{
die("ERROR: We could not connect.");
}
}
The function below is called from my model:
public function profile_information($account_name)
{
// Mysqli Variables
$sql = "SELECT $this->account_details.bio
FROM account_details
WHERE $this->account_details.account_name = ? LIMIT 10";
$type = array("s");
$param = array($account_name);
$results = $this->AC->Database->prepared_select_loop($sql, $type, $param);
$this->AC->Template->set_data('profile_information', $results, FALSE);
}
Once set within my model, I call the function within my controller and access it within my view with a foreach for displaying data:
$profile_information = $this->get_data('profile_information');
foreach ($profile_information as $row) :
//Displaying data here
endforeach;
The above works fine for displaying a large amount of data, but what I'm wanting to do is call the a database function that will allow me set individual data elements. Therefore not having to use a foreach if im only getting a limited amount of data (i.e. one row of Name, age, address)
A non dynamic way I have tackled this problem is to write the database for every function that only desires one row from the database:
function name($variable)
{
$sql = 'statement here';
$stmt = $this->AC->Mysqli->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result(bind results);
$stmt->fetch();
$this->AC->Template->set_data('id', $id);
$this->AC->Template->set_data('account_name', $account_name);
}
So in basically I want to make the above statement refactored into my database class and thus making it more dynamic.
I dont know how I would be able to tackle this problem, I don't want to use PDO, as I wish to find a solution within Mysqli. Any help would be appreciated.
Would you recommend just switching over to PDO?
Definitely.
Whole your function test() could be rewritten into three lines (assuming $param contains an array with parameters for the prepared statement):
public function test($sql, $param)
{
$stmt = $this->AC->pdo->prepare($sql);
$stmt->execute($param);
return $stmt->fetchAll();
}
I am using a function in php for all select queries so that i can dynamically retrieve data from my database ..... I just wanted to know that is my code secure and efficient or if their is a better way to do this, if so please point me to the right direction...thanks
class mysql {
private $conn;
function __construct(){
$this->conn= new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
if( mysqli_connect_errno() )
{
trigger_error('Error connecting to host. '.$this->connections[$connection_id]->error, E_USER_ERROR);
}
}
function extracting_data($table, $fields,$condition,$order,$limit){
$query="SELECT ".$fields."
FROM ".$table."
WHERE id =".$this->sql_quote($condition)."
ORDER BY ".$order."
LIMIT ".$limit." ";
//echo $query;
if($stmt = $this->conn->prepare($query)) {
$stmt->execute();
$row = array_pad(array(), $stmt->field_count, '');
$params = array();
foreach($row as $k=>$v) {
$params[] = &$row[$k];
}
call_user_func_array(array($stmt,'bind_result'),$params);
$result = array();
while($stmt->fetch()) {
foreach ($row as $b=>$elem) {
$vals[$b]=$row[$b];
}
$result[]=$vals;
}
$stmt->close();
return $result;
}
}
function sql_quote( $value )
{
if( get_magic_quotes_gpc() )
{
$value = stripslashes( $value );
}
//check if this function exists
if( function_exists( "mysql_real_escape_string" ) )
{
$value = mysql_real_escape_string( $value );
}
//for PHP version < 4.3.0 use addslashes
else
{
$value = addslashes( $value );
}
return $value;
}
}
Now to call the function I am using ::>
$connection=New mysql();
$extract=$connection->extracting_data("tablename","id,name,points","$_GET['id']","date desc","0,10");
The function returns a multi-dimensional array in $result and stores it in $extract ,depending on the data I want to extract..
Any improvements or other suggestions would be appreciated ...
Instead of binding the results and having to do loads of looping, you could simply use mysqli::query() and mysqli_result::fetch_all().
if($stmt = $this->conn->query($query)) {
$result = $stmt->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $result;
}
You would be better off binding your input variables rather than building up an SQL string containing them, but that may not be feasible using your current approach.
Edit
Sorry, I was an idiot and didn't notice that fetch_all() is only in PHP >= 5.3. You can still do this though which is simpler:
if($stmt = $this->conn->query($query)) {
$result = array();
while ($row = $stmt->fetch_assoc()) {
$result[] = $row;
}
$stmt->close();
return $result;
}
You should watch where the parameters for your function come from. If they come from an unreliable source, then it's very insecure.
If someone passes something like 1 ; DROP TABLE tablename ; SELECT * FROM dual WHERE 1 in the $condition parameter, you'll get the Little Bobby Tables scenario.
Your query will look like the following:
SELECT id, name, points
FROM tablename
WHERE id
ORDER BY
DATE DESC
LIMIT 0, 10
The id here will be casted to BOOLEAN, and the query will select all ids except 0 and NULL.
Is it really what you want?
You probably want to change your $condition to 'id = $id' or something like that.
Do you really need this level of abstraction: generating queries from uknown tables with unknown fields but with predefined SELECT / FROM / ORDER BY / LIMIT stucture?