0

What's the most common / best practice for handling possible MySQL errors in an PHP MVC application? Would it be best to pass an success boolean from the model to controller or to throw exceptions? Lets say I'm calling a stored procedure and the possible errors I could encounter are, db connection, the user does not have permission, invalid data, or a random MySQL error, what could be the most efficient / effective method for handling.

For example: Method 1:

//UserController.php
private function get_user_info(){
    $user_info = $user_model->read_user_info(123);

    if($user_info[0]){
        //Do stuff with user data
    }else{
        //Check if db, permission, invalid data, or random MySQL error
    }
}

//UserModel.php
public function read_user_info($read_user_id){
    $stmt = $db->prepare("CALL read_user_info(?, ?)");

    $stmt->bindParam(1, $current_user_id);
    $stmt->bindParam(2, $read_user_id);

    if($stmt->execute()){
        $result_set = $stmt->fetchAll(PDO::FETCH_ASSOC);

        //Does the user have permission to read other user's info
        if($result_set["granted"]){
            return array(true, $result_set["user_info"]);
        }else{
            return array(false, "Permission error");
        }
    }else{
        return array(false, "MySQL error");
    }
}

Method 2:

//UserController.php
private function get_user_info(){
    try{
        $user_info = $user_model->read_user_info(123);

        //Do stuff with user data
    }catch(ConnectionException $e){

    }catch(InvalidDataException $e){

    }catch(MySQLException $e){

    }
}

//UserModel.php
public function read_user_info($read_user_id){
    $stmt = $db->prepare("CALL read_user_info(?, ?)");

    $stmt->bindParam(1, $current_user_id);
    $stmt->bindParam(2, $read_user_id);

    if($stmt->execute()){
        $result_set = $stmt->fetchAll(PDO::FETCH_ASSOC);

        //Does the user have permission to read other user's info
        if($result_set["granted"]){
            return $result_set["user_info"];
        }else{
           throw new PermissionException();
        }
    }else{
        throw new MySQLException();
    }
}
  • The exception from data integrity violations should be handles in the data mappers (or other forms of abstracting the storage logic) within the model layer, when the interacting with domain objects. Data integrity problems should result in acquisition of error state with int the corresponding domain objects. The connection/credential exceptions probably within the services. When view requests data from model layer (by accessing services, that are part of model layer) it would be informed about the error states throughout model layer and combine the response from appropriate templates. – tereško Oct 12 '13 at 01:14
  • I'm not sure I 100% follow the first two sentences, could you reword them please? What I'm thinking now is that stored procedure should not need to worry about data integrity because it should already be taken care of by the validation method inside the model. – Mike Brown Oct 12 '13 at 01:48
  • 1
    [This](http://stackoverflow.com/a/5864000/727208) might help you with clearing up some of the terms. With "data integrity check", that will throw exceptions, I meant stuff `UNIQUE` or `FOREIGN` key violations. In other RDBMS (like postgre and oracle) you can also put additional `CONSTRAINTS` on columns like *"number must be between 10 and 99"*. Do not confuse it with validation rules that are part of domain logic. The instance, that checks if you entered email, should not be aware that there even exists a database. – tereško Oct 12 '13 at 02:14

1 Answers1

1

Would it be best to pass an success boolean from the model to controller or to throw exceptions?

It would be better that the whole error handling would be done in model as it is the proper place for "reusable" code throughout the MVC application concept.

what could be the most efficient / effective method for handling.

I say it is the Method 1. First of all, the controller receives only the user_info variable and applies custom logic on this event particularly without catching the various kinds of Exception and processing (IMHO, this should be centralized in model).

Anyhow, setting custom Error handlers might become very helpful - centralized approach, displaying of errors, log management, etc). An example Error class I use:

public static $error_types = array(
    E_ERROR => 'E_ERROR',
    E_WARNING => 'E_WARNING',
    E_PARSE => 'E_PARSE',
    E_NOTICE => 'E_NOTICE',
    E_CORE_ERROR => 'E_CORE_ERROR',
    E_CORE_WARNING => 'E_CORE_WARNING',
    E_COMPILE_ERROR => 'E_COMPILE_ERROR',
    E_COMPILE_WARNING => 'E_COMPILE_WARNING',
    E_USER_ERROR => 'E_USER_ERROR',
    E_USER_WARNING => 'E_USER_WARNING',
    E_USER_NOTICE => 'E_USER_NOTICE',
    E_STRICT => 'E_STRICT',
    E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
    E_DEPRECATED => 'E_DEPRECATED',
    E_USER_DEPRECATED => 'E_USER_DEPRECATED'
);

public static $throwables = array();

public static function set_throwable_handlers()
{
    error_reporting(E_ALL);
    ini_set('display_errors', FALSE);
    ini_set('log_errors', TRUE);
    ini_set('error_log', APP_DIR.DIR_SEP.'system'.DIR_SEP.'logs'.DIR_SEP.'error.log');

    set_error_handler(array('Error', 'error_handler'));
    set_exception_handler(array('Error', 'exception_handler'));

    register_shutdown_function(array('Error', 'shutdown_handler'));
}

public static function set_throwable($error_number, $error_text, $error_file, $error_line)
{
    self::$throwables[$error_number][] = array('type' => self::$error_types[$error_number], 'text' => $error_text, 'file' => $error_file, 'line' => $error_line);
}

public static function exception_handler(Exception $exception)
{
    self::set_throwable($exception->getCode(), $exception->getMessage(), $exception->getFile(), $exception->getLine());
}

public static function error_handler($error_number = '', $error_text = '', $error_file = '', $error_line = '')
{
    self::set_throwable($error_number, $error_text, $error_file, $error_line);
}

public static function shutdown_handler()
{
    $error = error_get_last();

    if ($error !== NULL)
    {   
        self::set_throwable($error['type'], $error['message'], $error['file'], $error['line']);

        View::display();
    }
}

public static function throw_error($error_text, $error_number = E_USER_NOTICE)
{
    trigger_error($error_text, $error_number);

    View::display();

    exit;
}
sitilge
  • 3,687
  • 4
  • 30
  • 56