简体   繁体   中英

How can I handle errors when there is transaction?

Here is my code:

try {
    $dbh_con->beginTransaction();

        $stmt1 = $dbh_conn->prepare("UPDATE activate_account_num SET num = num + 1");
        $stmt1->execute();

        $stmt2 = $dbh_con->prepare("SELECT user_id FROM activate_account WHERE token = ?");
        $stmt2->execute(array($token));
        $num_rows = $stmt2->fetch(PDO::FETCH_ASSOC);

        if ( $num_rows['user_id'] ){
            $_SESSION['error'] = 'all fine';

        } else {
            $_SESSION['error'] = 'token is invalid';
        }

    $dbh_con->commit();

    header('Location: /b.php');
    exit();

} catch(PDOException $e) {

    $dbh_con->rollBack();

    $_SESSION['error'] = 'something is wrong';
    header('Location: /b.php');
    exit();
}

As you see, my script rollbacks all queries when there is a exception. But it doesn't rollback when if ( $num_rows['user_id'] ){ is false . So how can I both rollback the queries and keep the error 'token is invalid' when that condition is false ?

Just throw an Exception and catch it like you already do. But instead of one catch statement have two:

try {
    $dbh_con->beginTransaction();

        $stmt1 = $dbh_conn->prepare("UPDATE activate_account_num SET num = num + 1");
        $stmt1->execute();

        $stmt2 = $dbh_con->prepare("SELECT user_id FROM activate_account WHERE token = ?");
        $stmt2->execute(array($token));
        $num_rows = $stmt2->fetch(PDO::FETCH_ASSOC);

        if ( $num_rows['user_id'] ){
            $_SESSION['error'] = 'all fine';

        } else {
            throw new \Exception('token is invalid');
        }

    $dbh_con->commit();

    header('Location: /b.php');
    exit();

} catch(PDOException $e) {

$dbh_con->rollBack();

$_SESSION['error'] = 'something is wrong';
header('Location: /b.php');
exit();
} catch(Exception $e) {

    $dbh_con->rollBack();

    $_SESSION['error'] = 'token is invalid';
    header('Location: /b.php');
    exit();
}

Your order of operations isn't good. You're making changes in the DB before you validate the token. This is bad security design. Always validate all inputs first, and only then make changes.

Secondly, the SELECT query that fetches the tokens doesn't need to be part of a transaction. Rolling back a SELECT has no impact because such a query makes no change to the DB. So I would do

try{
   select token
   if token is not found, set error and exit

   begin transaction
   update active_account_num
   ...other queries?
   end transaction and commit

   set success message, set header & exit
}catch{
   rollback
   set error message, set header & exit
}

In this case, because there is only one query that can change the DB, you don't even need a transaction.

Much easier, if you do the update after making sure that the table contains the values of interest

try {
    $stmt2 = $dbh_con->prepare("SELECT user_id FROM activate_account WHERE token = ?");
    $stmt2->execute(array($token));
    $num_rows = $stmt2->fetch(PDO::FETCH_ASSOC);

    if ( $num_rows['user_id'] ){
        $dbh_con->beginTransaction();
        $stmt1 = $dbh_conn->prepare("UPDATE activate_account_num SET num = num + 1");
        $stmt1->execute();

        $_SESSION['error'] = 'all fine';
        $dbh_con->commit();

    } else {
        $_SESSION['error'] = 'token is invalid';
        /* no transaction here, nothing to rollback */
    }

    header('Location: /b.php');
    exit();

} catch(PDOException $e) {

    $dbh_con->rollBack();

    $_SESSION['error'] = 'something is wrong';
    header('Location: /b.php');
    exit();
}

The calls to exit() are redundant too and can be removed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM