-1

I have the following PHP code:

$inserted = false ;
printf("Record must be inserted.  Old DB ID is %d.\n", $old_db_id) ;
$insertSQL = "INSERT INTO GoogleAuth ("
     .                               "script"
     .                             ", client_id"
     .                             ", client_secret"
     .                             ", refresh_token"
     .                             ", auth_token"
     .                             ", refresh_token_date"
     .                             ", auth_token_date"
     .                             ", client_info_date"
     .                             ", last_used_date"
     .                             ")"
     .            " VALUES(?, ?, ?, ?, ?, ?, ?, ?, NULL) "
                   ;
printf("Preparing statement....\n") ;
$stmt = $mySQL->prepare($insertSQL) ;
if ($stmt) {
    printf("Binding parameters...\n") ;
    if ($stmt->bind_param("sssssiiii"
                        , $page_file
                        , $clientId
                        , $clientSecret
                        , $refreshToken
                        , $authToken
                        , $authTokenDate
                        , $refreshTokenDate
                        , $clientInfoDate
                        , $now
                         )) {
        print("Bind returned true\n") ;
        if ($stmt->execute()) {
            $inserted = true ;
            }
        }
    else {
        printf("Bind failed.  %s\n", $stmt->error) ;
        }
    }
if ($inserted) {
    printf("row inserted.\n") ;
    }
else {
    printf("Insert failed! %s\n", $mySQL->error) ;
    trigger_error("Insert failed.  Dang it!", E_USER_ERROR) ;
    }

When I execute this code, the following is printed:

Record must be inserted.  Old DB ID is 0.
Preparing statement....
Binding parameters...
Bind failed.
Insert failed!

Note that the $stmt->error value and the $mySQL->error values are both empty!

Can you please help me understand how to reliably obtain the error message (and/or error code) associated with these sorts of failures? (I eventually figured out that I was putting 9 variables in place of 8 replacement markers... that's not the point. The point is to figure out how to get the actual errors from mysql.)

Thanks!

EDIT

By the way, I've seen articles like this that talk about interesting reuse of variable names. That does not apply here. $stmt is not used anywhere else in this application.

EDIT2

It was suggested that this is a duplicate of this problem. However, the difference is that the goal here is to obtain the failure message so that it can be analyzed for proper handling. (Some errors may be manageable, depending upon circumstances.) In any event, such process makes it possible to GRACEFULLY close down a program, rather than the rude and immediate result that comes from throwing failure errors. As I read the other problem, the goal is to ensure that an error gets to the log, and the program gets aborted.

Dennis
  • 1,071
  • 2
  • 17
  • 38
  • Does this answer your question? [Turning query errors to Exceptions in MySQLi](https://stackoverflow.com/questions/14578243/turning-query-errors-to-exceptions-in-mysqli) – Tangentially Perpendicular May 26 '21 at 03:44
  • It's not a MySQL error. It's a PHP error. Have you got PHP error reporting enabled? – Dharman May 26 '21 at 08:18
  • Semantics, @Dharman - depends upon your perspective. It is the result of asking MySQL to do something. And as I eluded to elsewhere, GRACEFUL handling of errors is essential to a good program. For example, if some INSERT fails due to duplicate key, it may be possible to adjust and continue. The alternative is to dump the whole process and leave it to the victim to figure out how to restart. – Dennis May 26 '21 at 12:32

2 Answers2

1

The failure (too many types) generates a warning, that's why stmt->error returns nothing, you can retrieve it by calling error_get_last function.

if(!$stmt->bind_param(....))
{
    $err = error_get_last();
    if($err)
        printf("Bind failed.  %s\n", $err['message']) ;
}
shingo
  • 18,436
  • 5
  • 23
  • 42
  • Thank you so much, @shingo ... this has troubled me for years! I wish the developers of MySQL had been a little bit more consistent about where to look for errors, but then again I've made a bad choice or two in my life. Well, probably anyway. :) – Dennis May 26 '21 at 11:02
  • @Dennis don't complain to the mysql developers, because it's php developers' work. Php checks the parameter count before it really does binding and throws a php level error if the count doesn't match the prepare statement. If there's any error while the mysql is binding parameters, the error goes to stmt->error. So you may also check it when bind_param returns false. – shingo May 26 '21 at 14:24
  • I'm trying to understand your comment @shingo. From a pure PHP perspective, the number of parameters is variable, right? and there's no way for PHP to say Good or Bad to this, without input from mysqli? From a PHP perspective, $stmt->bind_param("???", $val1) is perfectly valid. Only mysqli can determine that the parameters are not correct. But it doesn't put that fact into ->error. Instead, it relies on PHP to handle it. (Or more to the point, it makes the developer go looking for it.) That's an example of the "poor" choice I'm talking about. – Dennis May 26 '21 at 14:52
  • PHP knows, it will first check if `mysqli_stmt::$param_count`, the characters in the first parameter `$types` and the rest parameter count passed to bind_param are equivalent before calling a mysql function. – shingo May 27 '21 at 03:26
  • I guess PHP thinks this is an argument error rather than a mysql error. Because before calling mysql's bind_param function, PHP need allocate structures based on the parameter count defined in the prepare statement. PHP has no idea to fill them if the counts are not equivalent. – shingo May 27 '21 at 03:39
  • I wish php also was able to guess what the parameters of my functions should be so it could allocate buffers for me and save me that trouble too. :) That's a nice trick. As stated before, we're just going to disagree on this – Dennis May 31 '21 at 00:24
0

Following on the lead of @shingo - after accepting his answer, I have made the following function that I hope may help others along the way in this area.

function GetMySqlError($mysql = null, $stmt = null) {
    // MySQL error reporting is inconsistent.  Sometimes failure error
    // message will be in $mysql->error, sometimes in $stmt->error,
    // and sometimes it's necessary to inspect error_get_last().  Sheesh!

    // This routine will inspect all three, and will return the
    // FIRST nonblank value found in:
    //     stmt->error
    //     mysql->error
    //     error_get_last()['message']

    $messageMySql = "" ;
    $messageStmt  = "" ;
    $err          = null;
    $messageLast  = "" ;
    $returnValue  = null ;

    if (is_object($stmt) && property_exists($stmt, "error")) {
        $messageStmt = $stmt->error ;
        }
    if (is_object($mysql) && property_exists($mysql, "error")) {
        $messageMySql = $mysql->error ;
        }
    if ($err = error_get_last()) {
        $messageLast = $err['message'] ;
        }
    if (strlen($messageStmt) > 0) $returnValue = $messageMySql ;
    elseif (strlen($messageMySql) > 0) $returnValue = $messageStmt ;
    elseif (strlen($messageLast) > 0) $returnValue = $messageLast ;
    return $returnValue ;
    }

This may be called as such:

GetMySqlError($mysql_object) ;
GetMySqlError($myaql_object, $statement_object) ;
GetMySqlError($statement_object) ;

Example:
    printf("MySQL/statement error: %s\n", GetMySqlError($mySql, $stmt)) ;

Thanks again, @shingo

Note: I didn't deem it necessary to include connection errors here. That could be done, but I have my own routines for that already.

Note: This could be made more efficient. I opted for readability for this answer only.

Dennis
  • 1,071
  • 2
  • 17
  • 38
  • Why would you ever need something like this? Why not just enable PHP errors? – Dharman May 26 '21 at 11:47
  • If executing a method on a statement fails, then a Fatal error is thrown. If an error occurs from the connection object then a Fatal error is thrown. If you pass incorrect arguments to any method then a Fatal errors is thrown. When would this function ever come in handy? – Dharman May 26 '21 at 11:50
  • @Dharman, your assumptions are all incorrect. Warnings are thrown unless mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) ; is issued. As for your first comment, if you don't see the value, it is probably not for you. For myself, it helps in the debugging process. Also, GRACEFUL handling of errors is very important to me, and seems to be mostly a lost art. If an INSERT fails due to some duplicate key, do you consider it OK to fail the whole process? Or maybe handle it and continue? – Dennis May 26 '21 at 12:21
  • I do it this way https://stackoverflow.com/a/66285030/1839439 – Dharman May 26 '21 at 12:41
  • @Dharman "It is the difference of opinions that makes horse races." Mark Twain knew what he was talking about. That's pretty nice, but only handles duplicate key. Some other problem will still end up killing the process. I'm trying to avoid that for as many exceptions as possible, as stated before. – Dennis May 26 '21 at 13:21
  • I still think your premise is flawed. Mysqli errors are not special errors that you need to handle in a particular way. They are just like any other PHP error, once you enable mysqli error reporting. Graceful handling means that you handle the error in such a way that you let the user know how they can fix the problem. It has nothing to do with how the error is thrown. The good thing about PHP errors is that you can catch them if you want to and recover in a graceful way. With your way, you have to introduce a lot of extra code after every single method call. – Dharman May 26 '21 at 13:33