We have a bunch of websites using a shared MySQLi class.
PHP 7.4 was working with these and no errors were seen to be generated.
Now, PHP Bug #80837 has been fixed with release of PHP 7.4.18
The problem is that this fix means all our websites are throwing this fatal error and I can't see how to fix the error itself.
The Exception:
[06-May-2021 08:04:19 UTC] PHP Fatal error: Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command
now in /usr/local/lib/php/databaseClass.php:169 Stack trace:
#0 /usr/local/lib/php/databaseClass.php(169): mysqli_stmt->store_result()
#1 /usr/local/lib/php/databaseClass.php(897): Database->fetchResult(Object(mysqli_stmt), true)
#2 /home/account/public_html/.../file.php(45): DatabaseObject->getSelect('SELECT (bad_log...', Array, true)
#3 /home/katemawdsley/public_html/start.php(10): include('/home/account...')
#4 {main} thrown in databaseClass.php on line 169
The Database Class 'fetchResult' method:
/***
* For use with MySQLi->dbiLink Object.
* Returns the result as an array of KEY=>VALUE pairs from the Database.
* #param $result mysqli_result object.
* #return array|mixed
***/
private function fetchResult($result)
{
if ($result instanceof \mysqli_stmt) {
$result->store_result(); // This is line 169
$variables = [];
$data = [];
$meta = $result->result_metadata();
//error_log(print_r(debug_backtrace(),true));
while ($field = $meta->fetch_field()) {
$variables[] = &$data[$field->name]; // pass by reference
}
\call_user_func_array([$result, 'bind_result'], $variables);
$i = 0;
while ($result->fetch()) {
$resultsArray[$i] = [];
foreach ($data as $k => $v) {
$resultsArray[$i][$k] = $v;
if ($this->numericIndexes) {
$resultsArray[$i][] = $v;
}
}
unset($k, $v);
$i++;
}
} elseif ($result instanceof \mysqli_result) {
$rowNumber = 0;
while ($row = $result->fetch_assoc()) {
$resultsArray[$rowNumber] = $row;
if ($this->numericIndexes) {
foreach ($row as $numerical) {
$resultsArray[$rowNumber][] = $numerical;
}
unset($numerical);
}
$rowNumber++;
}
$i = 0;
unset($row, $rowNumber);
}
return $resultsArray;
}
The Database Class 'fetchResult' method called from a 'select' method:
/***
* Function for retrieving SQL query data from the Database
* #param $sql string the SQL select query
* #param null $data string|array the data to check in the query with
* #return array|bool|mixed
***/
public function select($sql, $data = null){
/***
* Now prepare the SQL
***/
try {
$query = $this->databaseObject->prepare($sql);
/****
* $query is prepared here....
***/
...
$query->execute();
}
catch (\mysqli_sql_exception $ex) {
$this->exceptionalError($ex);
return false;
}
catch (\Exception $ex) {
$this->exceptionalError($ex);
return false;
}
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
if ($query->field_count > 0 ) {
$query->store_result();
}
$this->rowsFound[] = $query->num_rows;
$output = $this->fetchResult($query); // Call to fetch result method
if ($query->field_count > 0) {
$query->free_result();
}
$query->close();
unset($query);
return $output;
}
/***
* NORETURN value so no result to return.
***/
$successValue = $this->rowsAffected[] = $query->affected_rows;
$query->close();
unset($query);
return $successValue;
}
What is the correct work around for how to construct this fetchResult method in light of the update?
(As you can tell we were completely unaware of this previous issue and it didn't appear on any error logs so was a surprise)
You are calling store_result() twice. You can't do that. Once the results have been buffered in PHP, the connection line is free - there are no more results pending to be fetched. Calling store_result() the second time will throw "Out of sync" error.
You call it first here:
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
if ($query->field_count > 0 ) {
$query->store_result();
}
and then here
private function fetchResult($result)
{
if ($result instanceof \mysqli_stmt) {
$result->store_result(); // This is line 169
Remove one usage and you should be fine.
I would recommend working with mysqli_result instead. When you call get_result it will buffer the results and give you a familiar object that you can work with. This could simplify your abstraction class significantly.
Here is how I would refactor this class:
public function select($sql, $data = null)
{
/***
* Now prepare the SQL
***/
try {
$query = $this->databaseObject->prepare($sql);
/****
* $query is prepared here....
***/
$query->execute();
} catch (\mysqli_sql_exception $ex) {
$this->exceptionalError($ex);
return false;
} catch (\Exception $ex) {
$this->exceptionalError($ex);
return false;
}
$result = $query->get_result();
if (\mb_strtoupper($reduce, "UTF-8") !== "NORETURN") {
$this->rowsFound[] = $result->num_rows;
if ($result) {
return $this->fetchResult($result); // Call to fetch result method
}
}
/***
* NORETURN value so no result to return.
***/
$successValue = $this->rowsAffected[] = $query->affected_rows;
return $successValue;
}
private function fetchResult(mysqli_result $result)
{
return $result->fetch_all($this->numericIndexes ? MYSQLI_BOTH : MYSQLI_ASSOC);
}
First of thank you for your help.
The code piece "while (sqlite_has_more($dres))" is using sqlite2 and I need sqlite3. If there isn't a replacement for has_more is there another code I can use to still Find whether or not more rows are available?
F.Y.I. The server updated their stuff which included their sqlite and now I have to fix this last peice of code to get the schedule to populate and not give me this error.
Fatal error: Non-static method SQLite3::open() cannot be called statically in /home/server/public_html/current-list.php on line 57
$row_num = 0;
if ($dbh = SQLite3::open($sked_path))
{
$qsql = "SELECT rowid,* FROM sked ORDER BY sk_dow_num, sk_time_start, sk_time_end";
$dres = SQLite3::query($dbh, $qsql);
if (SQLite3::num_Rows($dres) > 0)
{
$last_dow = "";
$last_start = "0000";
$last_end = "0000";
while (sqlite_has_more($dres))
{
$ska = Sqlite3Result::fetchArray($dres, SQLITE3_ASSOC);
$rid = $ska['rowid'];
$dow = $ska['sk_dow_name'];
$start = $ska['sk_time_start'];
$end = $ska['sk_time_end'];
$title = preg_replace("/<br\s*\/*>/", " ", $ska['sk_show_title']);
$show_dow = strtoupper($dow);
$show_start = strtoupper(formatTimeAmPm($start));
$show_end = strtoupper(formatTimeAmPm($end));
$show_style = "";
if (stristr($title, "Encore Show"))
$show_style = " class=\"$text_style\"";
Something like ...
<?php
$dbh = new SQLite3;
if ( !$dbh->open($sked_path) ) {
trigger_error('...error handling...', E_USER_ERROR);
}
else {
$dres = $dbh->query('
SELECT
rowid,*
FROM
sked
ORDER BY
sk_dow_num, sk_time_start, sk_time_end
');
if ( !$dres ) {
trigger_error('...error handling...', E_USER_ERROR);
}
else {
$ska = $dres->fetchArray(SQLITE3_ASSOC);
if ( !$ska ) {
onNoRecords();
}
else {
do {
doSomethingWithRowData($ska);
}
while( false!=($ska=$dres->fetchArray(SQLITE3_ASSOC)) );
}
}
}
(completely untested)
I have this function : it's work correctly,
function ms_get_did_detail($id) {
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2)) {
while($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC)) {
return $row2;
}
return 0;
}
return 0;
}
I want insert every element (every Dest & Priority) into MYSQL
if($info=ms_get_did_detail($value)) {
print_r($info);
$destination = $info['Dest'];
$priority = $info['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
It returns array like this :
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
[1]=> Array (
[Dest] => 200
[Priority] => 3
)
[2] => (
)
also , I have this function to insert value in database :
function my_did_destination($priority="",$destination="") {
global $link_voip;
$sql="INSERT INTO cc_did_destination (destination,priority)
VALUES ('$destination','$priority')";
$retval = mysql_query( $sql , $link_voip);
if(! $retval ) {
die('Could not enter data: ' . mysql_error());
}
}
but It's insert empty value within
You are inserting all rows with an ID of 0, so, if a row with id=0 already exists, it will fail and will not be inserted.
Maybe the easiest solution would be to make yout ID column autoincrement with an SQL statement like:
ALTER TABLE cc_did_destination
MODIFY COLUMN id INT auto_increment;
And then change your INSERT statement for:
$sql="INSERT INTO cc_did_destination (destination,priority)
VALUES ('$destination','$priority')";
Your $info is array of rows, it has numeric keys, not 'Dest'.
You should add index, like $dest = $info[0]['Dest'].
if($info=ms_get_did_detail($value))
{
print_r($info);
$dest = $info[0]['Dest'];
$priority = $info[0]['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
Or you can iterate through $info with a loop:
if($info=ms_get_did_detail($value))
{
foreach($info as $row) {
$dest = $row['Dest'];
$priority = $row['Priority'];
my_did_destination ($priority , $dest);
}
}
also, remove id from your insert statement
your array is:
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
[1]=> Array (
[Dest] => 200
[Priority] => 3
)
[2] => (
)
so it is a multidimensional array. if you need to insert all those entries, you shouldn't run multiple queries for the same thing. just use mysql batch insert syntax. (e.g. INSERT INTO tbl (col1,col2,col3) VALUES(a,b,c),(d,e,f),(g,h,i))
build the query string for insert.
foreach($a as $i => $v)
{
$b[] = '("'.$v['Dest'].'","'.$v['Priority'].'")';
}
$c = implode(',', $b);
$sql = "INSERT INTO cc_did_destination (destination,priority)
VALUES ".$c;
then run the query
N.B.
Please, don't use mysql_* functions in new code. They are no longer maintained and are officially deprecated. See the red box? Learn about prepared statements instead, and use PDO or MySQLi - this article will help you decide which. If you choose PDO, here is a good tutorial.
There are a couple of issues here.
Firstly your first function returns an array of arrays. Ie, it returns an array with subscript 0 for the first row (it only ever returns one rows details), which is an array containing that rows details.
You assign this to the $info variable, so it contains:-
[0]=> Array (
[Dest] => 100
[Priority] => 1
)
You then assign $info['Dest'] to $destination and $info['Priority'] to $priority. However neither of these exist. You would need $info[0]['Dest'] and $info[0]['Priority'].
2nd issue is that you are trying to assign a specific value to the auto increment id field. Just leave it out of the insert, or give it a value of null.
Quick rewrite and I would suggest you need something like this:-
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
foreach($info AS $info_row)
{
$destination = $info_row['Dest'];
$priority = $info_row['Priority'];
my_did_destination ($priority , $dest , $active , $did_voip , $cc_id);
}
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
function my_did_destination($priority="",$destination="")
{
global $link_voip;
$priority = mysql_real_escape_string($priority);
$destination = mysql_real_escape_string($destination);
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES (NULL,'$destination','$priority')";
$retval = mysql_query( $sql , $link_voip);
if(! $retval )
{
die('Could not enter data: ' . mysql_error());
}
}
EDIT
If you want to avoid multiple inserts unnecessarily then it might be easier to use an object. This way you can do the inserts easily when there are enough batched up (I normally do 255 at a time).
Something like this, although you probably should use mysqli_*
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
$insert_object = new insert_details($link_voip);
foreach($info AS $info_row)
{
$insert_object->set_row($info_row['Priority'], $info_row['Dest']);
}
unset($insert_object);
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2,MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
class insert_details()
{
private $db;
private $insert_row = array();
public function __CONSTRUCT($db)
{
$this->db = $db;
}
public function __DESTRUCT()
{
$this->do_insert();
}
public function set_row($priority="",$destination="")
{
$priority = mysql_real_escape_string($priority, $this->db);
$destination = mysql_real_escape_string($destination, $this->db);
$this->insert_row[] = "(NULL,'$destination','$priority')";
if (count($this->insert_row) > 255)
{
$this->do_insert();
}
}
private function do_insert()
{
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES ".implode(',', $this->insert_row);
$retval = mysql_query($sql, $this->db);
if(! $retval )
{
die('Could not enter data: ' . mysql_error());
}
$this->insert_row = array();
}
}
Quick rough mysqli_* equivalent, assuming that $link_voip is a mysqli connection. Note that prepared statements with bound parameters are an option (and it makes it harder to forget to escape variables), but it can become a bit messy when you are doing multiple inserts like this.
<?php
if($info=ms_get_did_detail($value))
{
print_r($info);
$insert_object = new insert_details($link_voip);
foreach($info AS $info_row)
{
$insert_object->set_row($info_row['Priority'], $info_row['Dest']);
}
unset($insert_object);
}
function ms_get_did_detail($id)
{
global $link;
$q2="select Dest,Priority from destpr where Id='$id'";
if($res2=mssql_query($q2))
{
if ($row2[]=mssql_fetch_array($res2, MSSQL_ASSOC))
{
while ($row2[]=mssql_fetch_array($res2, MSSQL_ASSOC))
{
}
return $row2;
}
else
{
return 0;
}
}
return 0;
}
class insert_details()
{
private $db;
private $insert_row = array();
public function __CONSTRUCT($db)
{
$this->db = $db;
}
public function __DESTRUCT()
{
$this->do_insert();
}
public function set_row($priority="",$destination="")
{
$priority = mysqli_real_escape_string($this->db, $priority);
$destination = mysqli_real_escape_string($this->db, $destination);
$this->insert_row[] = "(NULL,'$destination','$priority')";
if (count($this->insert_row) > 255)
{
$this->do_insert();
}
}
private function do_insert()
{
$sql="INSERT INTO cc_did_destination (id,destination,priority) VALUES ".implode(',', $this->insert_row);
$retval = mysqli_query($this->db, $sql);
if(! $retval )
{
die('Could not enter data: ' . mysqli_sqlstate($this->db));
}
$this->insert_row = array();
}
}
During my coding I really got stuck into this problem.
I ran a foreach loop and for every item I had to get a certain value from a function.
But I got only one returned. I could not figure out what was happening. I hope you guys surely will.
Below is the short version of my program.
Database structure is given at last.
<?php
function opendb() {
mysql_connect("localhost", "root", "root");
mysql_select_db("something_db");
}
function sql_query($sql) {
$datas = array();
if ($res = mysql_query($sql)) {
$x = 0;
while ( $data = mysql_fetch_assoc($res) ) {
$datas[$x] = $data;
$x += 1;
}
}
return $datas;
}
function get_parent_id($table, $parent, $cid) {
// cid=>child id
$sql = "SELECT * FROM $table WHERE id=$cid";
$datas = sql_query($sql);
$pid = $datas[0]['parent'];
$p_id = $datas[0]['id'];
if ($pid != 0) {
get_parent_id($table, $parent, $pid);
} else {
return $p_id;
}
}
opendb();
$datas_pkg = sql_query("SELECT * FROM tbl_packages WHERE 1");
foreach ( $datas_pkg as $data_pkg ) {
echo $data_pkg['destination_id'] . '-->';
echo $parent_id = get_parent_id('tbl_destinations', 'parent', $data_pkg['destination_id']);
echo '<br/>';
}
?>
Database structure..
tbl_destinations
+--------+-------------------------+-----------+
| id(int)|destination_name(Varchar)|parent(int)|
+--------+-------------------------+-----------+
tbl_packages
+-------+---------------------+-------------------+
|id(int)|package_name(varchar)|destination_id(int)|
+-------+---------------------+-------------------+
If I did not clear my question please let me know so that I can help you to help me.
if($pid!=0)
{
get_parent_id($table,$parent,$pid);
}
You call the function, but never use its value.
I have the following code:
function process_bulk_action() {
if (isset($_GET['locations'])) {
$location_ids = ( is_array( $_GET['locations'] ) ) ? $_GET['locations'] : array( $_GET['locations'] );
global $wpdb;
switch ( $this->current_action() ) {
case 'edit':
bulk_edit($location_ids);
break;
case 'delete':
bulk_delete($locations_ids);
break;
default:break;
}
}
}
function bulk_delete($ids) {
foreach ( $ids as $id ) {
$id = absint( $id );
$sql = "DELETE FROM wp_nc_location WHERE location_id = $id";
$delete = $wpdb->query( $sql );
}
}
function bulk_edit($ids) {
foreach ( $ids as $id ) {
$id = absint( $id );
$sql = "SELECT name FROM wp_nc_location WHERE location_id = $id";
$select = $wpdb->query( $sql );
echo 'select: '. $select. ',';
print_r($select);
}
}
However I am getting the following error message when I try to call either bulk_edit or bulk_delete from inside that switch statement above:
Fatal error: Call to undefined function bulk_delete
I realize I am getting something wrong with the scope but I'm not sure where to put the functions bulk_edit or bulk_delete...
I'm guessing from your use of $this-> in various places that those functions belong to a class? In this case, you have to call the function like... $this->bulk_delete(..arguments..);