1

My PHP-coded JOIN fails My SQL JOIN is fine as it works from the SQL command line - but while my PHP code will return one or more records from a single table, it fails when I JOIN with:

Uncaught Error: Call to a member function get_result() on array

My environment: Windows 8.1.6 10.4.24-MariaDB Apache/2.4.53 (Win64) libmysql - mysqlnd 8.1.6 OpenSSL/1.1.1n PHP/8.1.6

The following query works from the sql command line. It is the one I would like PHP to resolve for me:

SELECT productoptionsr.contentnamekey,content.contentname LEFT JOIN content ON productoptionsr.contentnamekey=content.contentnamekey FROM productoptionsr WHERE businessnamekey='8c9007ab5e9942a80b19887057511d7b1f3c5bbea8672dc03f4fd8291f6c18ab' AND ponamekey='c91bafa8aebfb760547c761110441790f263d3c23721a5034b4d26e97b231668' ORDER BY seq;

My PHP code (a library that I put together in part using code from php.net help text)

function DBExecute($DB, $sql)
{ // A wrapper to prepare, execute a query and return $stmt
  //////////////////////////////////////////////////////////////////////////////
  global $sql_request;
  $sql_request=$sql;
  //////////////////////////////////////////////////////////////////////////////
  try { $stmt = $DB->prepare($sql); }
  catch (Exception $e)
  { // Handle the error, something went wrong
    error_log($e->getMessage());
    return DBState($DB, [ "errno"=>$e->getCode(), "error"=>$e->getMessage() ]);
  }

  try { $stmt->execute(); }
  catch (Exception $e)
  { // Handle the error, something went wrong
    error_log($e->getMessage());
    return DBState($DB, [ "errno"=>$e->getCode(), "error"=>$e->getMessage() ]);
  }
  return $stmt;
}

function DBSelect($DB, $table, $selection, $conditions, $limit=0)
{ // A simple wrapper to prepare, select and return records
  global $receipt, $sql_request;
  if($limit>0)
  { $conditions="$conditions LIMIT $limit"; }
  $sql="SELECT $selection FROM $table WHERE $conditions";
  $sql_request=$sql;
  //////////////////////////////////////////////////////////////////////////////
  $result=[];
  try
  { $stmt=DBExecute($DB, $sql);

    if (!($res = $stmt->get_result()))
    { Abort("DBSelectData get_result() failed: (" . $stmt->errno . ") " . $stmt->error ." $sql"); }
    //////////////////////////////////////////////////////////////////////////////
    for ($row_no = ($res->num_rows - 1); $row_no >= 0; $row_no--) {
        $res->data_seek($row_no);
        $result[]=$res->fetch_assoc();
    }
    //////////////////////////////////////////////////////////////////////////////
    // Reverse our reverse array to put it into correct order
    if(isset($result))
    {$result=array_reverse($result);}
    $stmt->close();
    return $result;
  } catch (Exception $e)
  {
    // Handle the error, something went wrong
    error_log($e->getMessage());
    return DBState($DB, [ "errno"=>$e->getCode(), "error"=>$e->getMessage() ]);
  }
  return true;
}

function DBState($DB=false, $stmt=false)
{ // Return state of play, give precedence to prepare/execute errno
  //////////////////////////////////////////////////////////////////////////////
  global $sql_request;
  //////////////////////////////////////////////////////////////////////////////
  if($stmt&&$stmt["errno"]>0)
  { return [ "affected_rows"=>0, "insert_id"=>0, "errno"=>$stmt["errno"], "error"=>$stmt["error"], "query"=>"$sql_request", "A"=>mt_rand(2,32767) ]; }

  return [ "affected_rows"=>mysqli_affected_rows($DB), "insert_id"=>mysqli_insert_id($DB), "errno"=>$DB->errno, "error"=>$DB->error, "query"=>"$sql_request", "B"=>mt_rand(2,32767) ];
}

function DBConnect($DBHOST, $DBUSER, $DBPASS, $DBINSTANCE)
{ // Connect to the db and return a pointer

  // Enable DB exceptions
  mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

  // Connect
  $DB=new mysqli("$DBHOST", "$DBUSER", "$DBPASS", "$DBINSTANCE");
  if($DB->connect_errno)
  {
    $report["DBHOST"]="$DBHOST";
    $report["DBUSER"]="$DBUSER";
    if(mb_strlen("$DBPASS")>4&&"$DBPASS"!=="false")
    { $report["DBUSER"]="true"; } else { $report["DBUSER"]="false"; }
    $report["DBINSTANCE"]="$DBHOST";
    $report["errno"]=$DB->connect_errno;
    $report["error"]="Connect error: ".$DB->connect_error;
    Abort( json_encode( $report, JSON_PRETTY_PRINT) );
  }
  // Set char set
  mysqli_set_charset($DB, 'utf8mb4');
  return $DB;
}

function DBClose($DB)
{ // Disconnect from db
  $result=DBState($DB, [ "errno"=>0, "error"=>"" ]);
  $DB->close();
  return $result;
}

DBSelect($DB, "$TABLE[PRODUCTOPTIONSR]",
    "PRODUCTOPTIONSR.contentnamekey, CONTENT.contentname INNER JOIN CONTENT ON PRODUCTOPTIONSR.contentnamekey=CONTENT.contentnamekey",
    "businessnamekey='8c9007ab5e9942a80b19887057511d7b1f3c5bbea8672dc03f4fd8291f6c18ab' AND ponamekey='c91bafa8aebfb760547c761110441790f263d3c23721a5034b4d26e97b231668' ORDER BY seq");
Dharman
  • 30,962
  • 25
  • 85
  • 135
Ran Bo
  • 33
  • 8
  • Could you please create [mcve]? – Dharman Oct 08 '22 at 21:12
  • I don't have access to a public web server - it's all local on my laptop only so, unfortunately, no. – Ran Bo Oct 08 '22 at 21:13
  • I meant can you reduce the code example to a few lines of code? – Dharman Oct 08 '22 at 21:14
  • 1
    You need to stop manually checking for errors. Please read: [Should we ever check for mysqli_connect() errors manually?](https://stackoverflow.com/q/58808332/1839439) and [Should I manually check for errors when calling “mysqli_stmt_prepare”?](https://stackoverflow.com/q/62216426/1839439) – Dharman Oct 08 '22 at 21:14
  • if (!($res = $stmt->get_result())) is where it appears to bail - and my belief is $res is the problem but that's guesswork from my side. – Ran Bo Oct 08 '22 at 21:15
  • 1
    Your `DBExecute()` function can return a statement or an array, so you should double check the returned values. But to be honest, this code is _not_ good. If you want to abstract the actual DB code away from your business code, use some tried and tested query builder or ORM instead. – M. Eriksson Oct 08 '22 at 21:18
  • Fair comment on reducing to a few lines of code. It will take me a little longer but yes, I will change my post in the morning. – Ran Bo Oct 08 '22 at 21:19

1 Answers1

1

Your code is very messy due to all the superfluous error checking, which has led you to your current problem. You are catching the error and then returning an array from DBState which then bubbles up to DBExecute. Thus, somewhere there you have a suppressed error which causes your code to execute get_result on an array. Fix your return types and remove all this error checking.

Here's your code after a slight tyding up:

<?php

/**
 * A wrapper to prepare, execute a query and return $stmt
 */
function DBExecute($DB, $sql): mysqli_stmt
{
    $stmt = $DB->prepare($sql);
    $stmt->execute();
    return $stmt;
}

/**
 * A simple wrapper to prepare, select and return records
 */
function DBSelect($DB, $table, $selection, $conditions, $limit = 0): array
{
    if ($limit > 0) {
        $conditions = "$conditions LIMIT $limit";
    }
    $sql = "SELECT $selection FROM $table WHERE $conditions";

    $stmt = DBExecute($DB, $sql);

    return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}

/**
 * Connect to the db and return a pointer
 */
function DBConnect($DBHOST, $DBUSER, $DBPASS, $DBINSTANCE): mysqli
{
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

    $DB = new mysqli("$DBHOST", "$DBUSER", "$DBPASS", "$DBINSTANCE");
    $DB->set_charset('utf8mb4');
    return $DB;
}

It's far from perfect, but at least now the actual error will show up in your error logs. It's still missing parameter type specifications.

Lesson learned: be very careful with try-catch. Use it only if you are sure that you actually need it.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • Wow! Thanks for your effort - I started my day with taking your initial suggestion of simpler code example and doing that now. I also accept your constructive criticism: I could tone down my error checking. I have a reasonable understanding of Objects/Methods/Properties from Javascript, and thought that as a starting point might help with PHP, but as yet, I'm far from comfortable. Thanks for the code, the explanations and the link in your earlier comments. – Ran Bo Oct 09 '22 at 12:23
  • You have ": mysqli_stmt", ": array", ": mysqli" at the end of each line that starts a function. It's new to me - what does this do to the function, what does it imply when processing? – Ran Bo Oct 09 '22 at 12:25
  • 1
    It's a return type declaration https://www.php.net/manual/en/language.types.declarations.php – Dharman Oct 09 '22 at 14:22
  • I created a new post with easier sample code - it (mostly) works, I feel more confident supporting. If you can take a look at it, it would be great. (and genuine thanks for your help above). https://stackoverflow.com/questions/74006557/learning-mysqli-procedural-coding-the-update-gives-unexpected-results – Ran Bo Oct 09 '22 at 16:37