CodeIgniter: Try Catch is not working in model cla

2020-01-27 01:41发布

问题:

I have a unique constraint on of the column. When this code runs, I get error log from framework but it is not what I have given in Exception block.
If unique column exists then I want to query its primary key and set it as $id and return to the page. Right now it is stopping on db error and not going into Catch block.
Here is my code:

try {
            $result = $this->db->insert('email', $new_email);
            if ($result)
            {
                $id = $this->db->insert_id();    
            } else {
                throw new Exception("$$$$$$$$$$$$$Log database error");
            }
        } catch (Exception $e) {
            log_message('error',$e->getMessage());
            return;
        }

**Error Messages** I get from framework:

DEBUG - 2013-04-07 05:00:38 --> DB Transaction Failure
ERROR - 2013-04-07 05:00:38 --> Query error: Duplicate entry 

I don't know what is wrong with it.

回答1:

CI has no good support for exceptions. You DB queries will call some vague CI error_logging thingie called show_error(). What you need to do is setup proper exception handling.

Basically you can follow the entire recipe.

Now all your database errors will automatically throw exceptions. And as a bonus you have good exception handling in your entire CI application.

Register a custom errorhandler that transforms PHP errors into exceptions, for instance put this in top of your config/config.php

function my_error_handler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno))
    {
        // This error code is not included in error_reporting
        return;
    }
    log_message('error', "$errstr @$errfile::$errline($errno)" );
    throw new ErrorException( $errstr, $errno, 0, $errfile, $errline );
}
set_error_handler("my_error_handler");

Register an uncaught exception handler, put something like this in your config/config.php

function my_exception_handler($exception)
{
    echo '<pre>';
    print_r($exception);
    echo '</pre>';
    header( "HTTP/1.0 500 Internal Server Error" );
}
set_exception_handler("my_exception_handler");

Set a termination handler:

function my_fatal_handler()
{
    $errfile = "unknown file";
    $errstr  = "Fatal error";
    $errno   = E_CORE_ERROR;
    $errline = 0;
    $error = error_get_last();
    if ( $error !== NULL )
    {
        echo '<pre>';
        print_r($error);
        echo '</pre>';
        header( "HTTP/1.0 500 Internal Server Error" );
    }
}
register_shutdown_function("my_fatal_handler");

Set a custom assert handler that converts asserts into exceptions, put something like this in your config/config.php:

function my_assert_handler($file, $line, $code)
{
    log_message('debug', "assertion failed @$file::$line($code)" );
    throw new Exception( "assertion failed @$file::$line($code)" );
}
assert_options(ASSERT_ACTIVE,     1);
assert_options(ASSERT_WARNING,    0);
assert_options(ASSERT_BAIL,       0);
assert_options(ASSERT_QUIET_EVAL, 0);
assert_options(ASSERT_CALLBACK, 'my_assert_handler');

Use wrappers like this in your controllers

public function controller_method( )
{
    try
    {
        // normal flow
    }
    catch( Exception $e )
    {
        log_message( 'error', $e->getMessage( ) . ' in ' . $e->getFile() . ':' . $e->getLine() );
        // on error
    }
}

You can tune and customize the whole thing to your likings!

Hope this helps.

You will also need to intercept the CI show_error method. Place this in application/core/MY_exceptions.php:

class MY_Exceptions extends CI_Exceptions
{
    function show_error($heading, $message, $template = 'error_general', $status_code = 500)
    {
        log_message( 'debug', print_r( $message, TRUE ) );
        throw new Exception(is_array($message) ? $message[1] : $message, $status_code );
    }
}

And leave in application/config/database.php this setting on FALSE to have database errors converted into exceptions.

$db['default']['db_debug'] = TRUE;

CI has a few (very) weak points, such as exception-handling but this will go a long way correcting that.

If you are going to use transactions, make sure you do rollbacks on exceptions. Related to this NEVER (as in EVER) use persistant connections as open transactions and other session specific DB state will be picked up / continued by other sessions.



回答2:

If database debugging is turned on, errors from the DB will be routed to the Exceptions core class, and exit() is called after, meaning the script will never even reach your if conditional.

Open application/config/database.php and try setting db_debug to false. This is a good idea to do for production websites, anyways, because you don't want any SQL query issues to release info about your database structure.

Also, unrelated, be careful using $ inside of double quotes, because it'll get parsed as a variable (even a bunch of them in a row -- it'll literally be a variable variable variable variable ...).



回答3:

What if you set the result outside the try block? Or an ternary operator:

$result = $this->db->insert('email', $new_email);
try {
  $result = ($result) ? $result || false;  
  if ($result) {
    $id = $this->db->insert_id();    
  } else {
    throw new Exception("$$$$$$$$$$$$$Log database error");
  }
} catch (Exception $e) {
  log_message('error',$e->getMessage());
  return;
}

It's hard to know without knowing what the value of $result really is, like @jcorry said.



回答4:

$db['default']['db_debug'] = FALSE;

https://forum.codeigniter.com/archive/index.php?thread-8484.html



回答5:

If you look into CodeIgniter's code, you will see that it has its own error handling set up in CodeIgniter.php in bot system/core and ci_system/core folders.

/*
 * ------------------------------------------------------
 *  Define a custom error handler so we can log PHP errors
 * ------------------------------------------------------
 */
    set_error_handler('_error_handler');
    set_exception_handler('_exception_handler');
    register_shutdown_function('_shutdown_handler');

The first two functions deal with the error handling. In order to get your try..catch block to work, you just need to turn this off.

Using the restore_error_handler and restore_exception_handler functions, we can reset these error handling functions back to default.

    restore_error_handler();
    restore_exception_handler();

or... if you want to preserve the error handlers to restore them later.

    //Disable CodeIgniter error handling so we can use try catch blocks
    $errorHandlers = array();
    do {
        $errorHandler = set_error_handler(function() {}); //Get the current handler
        array_push($errorHandlers, $errorHandler); //Store the handler so that it can be restored if necessary
        for ($i = 0; $i < 2; $i++) { //We have to reset twice to get to the previous handler since we added a dummy handler
            restore_error_handler();
        }
    }
    while (!is_null($errorHandler)); //Is null when there are no handlers
    $exceptionHandlers = array(); //Do the same with exceptions
    do {
        $exceptionHandler = set_exception_handler(function() {});
        array_push($exceptionHandlers, $exceptionHandler);
        for ($i = 0; $i < 2; $i++) {
            restore_exception_handler();
        }
    }
    while (!is_null($exceptionHandler));

    try {
        //Your code goes here
    }
    catch (Error $e) {
        //Handle error
    }
    catch (Exception $e) {
        //Handle exception
    }

    //Restore all error handlers
    foreach ($errorHandlers as $errorHandler) {
        if (isset($errorHandler)) {
            set_error_handler($errorHandler);
        }
    }
    foreach ($exceptionHandlers as $exceptionHandler) {
        if (isset($exceptionHandler)) {
            set_exception_handler($exceptionHandler);
        }
    }

We can disable all the custom error handlers and store them to be restored if desired.

I use the loops when saving and restoring in case there are other error handlers set up. This way, you restore all the error handlers as there may be similar code elsewhere that goes back to a previous error handler for some specific purpose and it will be preserved.