PHP: nested try-catch does not work within a function - php

Recently I have started playing around with Exceptions and their handling. Everything seemed to work just fine however I have just hit a brick wall and I'm not sure if there is a solution for my problem or should I remake my code base.
Situation:
I'm executing a small piece of code inside try block and I'm catching all exceptions and simply logging/outputing the errors. I'm trying to execute a function which deletes object from database:
It removes the relationships;
It removes the object.
What I want to accomplish is: if object removal fails - restore previously removed relationships.
I thought that putting a nested try-catch block inside a function at the place where the final deletion is being performed I would catch an exception and revert the changes, however this does not work inside a function and it simply ignores the try-catch block and goes to the top one.
HOWEVER: When I put the same piece of code outside of the function it works just fine and like intended!
Below are two pieces of code - working and not working.
EDIT: I forgot to mention that I purposely made a syntax mistake in "router" DELETE query and it always throws an exception during "$status_r = \sys\database\query($query, $db, $elem_t, $elem);" execution. In the first case it ignores the try-catch block inside the function "delete" and goes to the top try-catch block where function is called.
NOT WORKING AS INTENDED
try
{
$db = \sys\database\connect();
$status = ips\routers\delete($id, $db);
echo "success";
\sys\core\log_action("router_delete", "success", "Router '$id' successfully deleted", $db);
}
catch (Exception $ex)
{
echo (sys\errors\is_error($ex->getMessage())) ? sys\errors\parse($ex->getMessage()) : $ex->getMessage();
\sys\core\log_action("router_delete", "failure", $ex->getMessage(), $db);
}
Code inside delete function:
function delete($id, &$db = false)
{
// Check if Any Interfaces are set on this router
if (\ips\interfaces\exists(array("router", $id), $db) !== false)
throw new \Exception(\sys\errors\exist("Router has active interfaces and cant be removed!"));
$curr_ip_id = \ips\ips\get(array("router_id", $id), "id", $db);
if (!\sys\validation\validate("numeric", $curr_ip_id[0]['id']))
throw new \Exception(\sys\errors\database());
$status = \ips\ips\revoke($curr_ip_id[0]['id'], NULL, $db);
if ($status === true)
{
// if we get an error we need to revert changes - aka rebind IP to router
try
{
$query = "DELETE FROM routers WHERE i = ?";
$elem_t = "i";
$elem = array($id);
$status_r = \sys\database\query($query, $db, $elem_t, $elem);
if ($status_r === true)
return true;
else
throw new \Exception();
}
catch (Exception $ex)
{
$status_rebind = \ips\ips\set_owner(array("id", $curr_ip_id[0]['id']), $id, "router", $db);
if ($status_rebind === true)
throw \Exception(\sys\errors\database("Failed Deleting router at the last step. IP reassigned successfully!"));
else
throw \Exception(\sys\errors\database("Failed Deleting router at the last step. IP reassigned failed!"));
}
}
else
throw new \Exception(\sys\errors\database("Failed revoking IP address from router!"));
}
WORKING AS INTENDED
try
{
$db = \sys\database\connect();
if (\ips\interfaces\exists(array("router", $id), $db) !== false)
throw new \Exception(\sys\errors\exist("Router has active interfaces and cant be removed!"));
$curr_ip_id = \ips\ips\get(array("router_id", $id), "id", $db);
if (!\sys\validation\validate("numeric", $curr_ip_id[0]['id']))
throw new \Exception(\sys\errors\database());
$status = \ips\ips\revoke($curr_ip_id[0]['id'], NULL, $db);
if ($status === true)
{
// if we get an error we need to revert changes - aka rebind IP to router
try
{
$query = "DELETE FROM routers WHERE i = ?";
$elem_t = "i";
$elem = array($id);
$status_r = \sys\database\query($query, $db, $elem_t, $elem);
if ($status_r === true)
return true;
else
throw new \Exception();
}
catch (Exception $ex)
{
$status_rebind = \ips\ips\set_owner(array("id", $curr_ip_id[0]['id']), $id, "router", $db);
if ($status_rebind === true)
throw new \Exception(\sys\errors\database("Failed Deleting router at the last step. IP reassigned successfully!"));
else
throw new \Exception(\sys\errors\database("Failed Deleting router at the last step. IP reassigned failed!"));
}
}
else
throw new \Exception(\sys\errors\database("Failed revoking IP address from router!"));
}
catch (Exception $ex)
{
echo (sys\errors\is_error($ex->getMessage())) ? sys\errors\parse($ex->getMessage()) : $ex->getMessage();
\sys\core\log_action("router_delete", "failure", $ex->getMessage(), $db);
}
I'm not exactly sure why try-catch does not work inside the function. Any help would be highly appreciated.
Thanks in advance!

Related

Will the code in finally be run if I have program flow like this?

I was wondering if the following program flow will prevent the finally from being run in this try-catch-finally block due to a return in the try.
Please excuse poor error checking and sanitisation, this is just a mock:
function doLogin() {
$dbh = new PDO('mysql:host=localhost;dbname=test', "root", "");
$errors = array();
$loginSuccess = false;
try {
$query = $dbh->prepare('SELECT *
FROM users
WHERE username = :username');
$query->bindParam(':username', $_POST['username'], PDO::PARAM_STR);
$result = $query->fetch(PDO::FETCH_ASSOC);
if (!password_verify($_POST['password'], $result['password'])) {
array_push($errors, 'Invalid password.');
return; // will this return prevent the finally from being run?
}
else {
$loginSuccess = true;
}
}
catch (PDOException $pdoEx) {
echo 'Error occurred ' . $pdoEx->getMessage();
}
finally {
$dbh = null;
}
}
Code is pretty poorly written, but hopefully you understand my point.
The answer is yes, the code in finally will be run.
For example:
function example() {
try {
return true;
}
catch(Exception $e){
echo "error";
}
finally{
return false;
}
}
var_dump( example() );
Outputs:
bool(false)
It's also (hiddenly) stated in the docs:
In PHP 5.5 and later, a finally block may also be specified after the
catch blocks. Code within the finally block will always be executed
after the try and catch blocks, regardless of whether an exception has
been thrown, and before normal execution resumes.
To my understanding the try block finishes with the return statement. The normal execution is "paused" and the finally executed.

Is it possible to have Try/Catch Throw with multiple exceptions

i have the following code and i'm wondering if i can use try & catch as below:
class fun_database implements idbInfo{
private $srvr=idbInfo::srvr_name;
private $usr=idbInfo::usrnm;
private $pass=idbInfo::psswrd;
private $db=idbInfo::db_name;
public function connct(){
$hookup = new mysqli($this->srvr, $this->usr, $this->pass, $this->db);
if ($hookup->connect_errno)
{
throw new Exception("Error Processing Request", 1);
}
}
public function sql_require_all($table_name, $table_col){
$hookup = new connct();
$result = $hookup->query("SELECT $table_col FROM $table_name");
if($hookup->error()){
throw new Exception("Error Processing Request", 1);
}
return $result->num_rows;
}
}
This is a simple connection to the mysql and performing some querying there. Here is and the actual call of the functions above:
$conn = new fun_database();
try{
$result = $conn->sql_require_all('wordtypes', 'types');
}
catch(Exception $err){
echo "Problems at:". $err->getMessage();
}
return "<option>".$result."</option>";
What i'm asking is a bit theory. Most probably this code is NOT WORKING (i didn't test it yet). I just want to know is it possible with one 'try' to 'catch' two exceptions (as you can see the first 'throw' is in the second method of fun_database, and the second 'throw' is in the first method of the same object which is only called from the second method).
sorry for making it too complicated but still can't figure it out id this structure of try/catch is working.
you can only catch different types of exception...
class fun_database implements idbInfo{
private $srvr=idbInfo::srvr_name;
private $usr=idbInfo::usrnm;
private $pass=idbInfo::psswrd;
private $db=idbInfo::db_name;
public function connct(){
$hookup = new mysqli($this->srvr, $this->usr, $this->pass, $this->db);
if ($hookup->connect_errno)
{
throw new DomainException("Error Processing Request", 1);
}
}
public function sql_require_all($table_name, $table_col){
$hookup = new connct();
$result = $hookup->query("SELECT $table_col FROM $table_name");
if($hookup->error()){
throw new Exception("Error Processing Request", 1);
}
return $result->num_rows;
}
}
Then:
try{
$conn = new fun_database();
$result = $conn->sql_require_all('wordtypes', 'types');
}
catch(DomainException $err){
echo "This Problem at:". $err->getMessage();
}
catch(Exception $err){
echo "That Problem at:". $err->getMessage();
}
return "<option>".$result."</option>";
you would need your class instantiation inside that try block though I believe.
It wouldn't catch the two exceptions because as soon as the first exception is thrown, it goes straight to the catch block, thereby skipping the second exception directly.
You could wrap each code which may throw an exception in its own try-catch block.
Yes and no. Your code is able to catch two of this exceptions but not both of them at the same time. When one of exception will be thrown, program execution will look for closest catch block, which fits to catch Exception class. Rest of code will be omitted.
You can throw an exception at an point in the program (not after an excpetion if it is not caught).
As soon as it hits this point it will stop and try to make the fallback to the a try catch block. As soon as it finds one it will do this block (if it is a good catch)
You could make a try catch around your entire program or just a function.
You can throw different classes of exceptions:
class ConnectException extends Exception {}
class QueryException extends Exception {}
and then catch different exceptions:
try {
// something
}
catch (ConnectException $ex) {
// connect exception
}
catch (QueryException $ex) {
// query exception
}
It is not possible because when you throw
throw new Exception("Error Processing Request", 1);
this exception it will be caught in this line
catch(Exception $err){
echo "This Problem at:". $err->getMessage();
}
you will not reach the line that can throw the other exception if first exception was thrown

Global php PDO handling errors

I use try/catch block in my classes methods, If a get an exception, I log the error. But I would like to tell the "User" that a database query/etc failed - and the problem should be fixed soon.
I could use a die() on the Exception in my methods, but that wouldn't be DRY, as I would have to retype it a lot, so any suggestions on how I can do this.
Example method:
public function login($username, $password) {
try {
$this->STH = $this->DBH->prepare("SELECT id, baned, activated FROM users WHERE username = ? AND password = ?");
$this->STH->setFetchMode(PDO::FETCH_OBJ);
$this->STH->execute(array($username, $password));
if (($row = $this->STH->fetch()) !== false)
return $row;
} catch (PDOException $e) {
//Log $e->getMessage();
die('A database error occoured, we are working on the problem, and it should work in a few...');
}
}
If you need a quick fix, you can set a global exception handler, like this:
function pdo_exception_handler($exception) {
if ($exception instanceof PDOException) {
// do something specific for PDO exceptions
} else {
// since the normal exception handler won't be called anymore, you
// should handle normal exceptions yourself too
}
}
set_exception_handler('pdo_exception_handler');
It's OK to repeat yourself in this case because as each instance of die() passes a unique message.

PHP Exceptions in Classes

I'm writing a web application (PHP) for my friend and have decided to use my limited OOP training from Java.
My question is what is the best way to note in my class/application that specific critical things failed without actually breaking my page.
My problem is I have an Object "SummerCamper" which takes a camper_id as it's argument to load all of the necessary data into the object from the database. Say someone specifies a camper_id in the query string that does not exist, I pass it to my objects constructor and the load fails. I don't currently see a way for me to just return false from the constructor.
I have read I could possibly do this with Exceptions, throwing an exception if no records are found in the database or if some sort of validation fails on input of the camper_id from the application etc.
However, I have not really found a great way to alert my program that the Object Load has failed. I tried returning false from within the CATCH but the Object still persists in my php page. I do understand I could put a variable $is_valid = false if the load fails and then check the Object using a get method but I think there may be better ways.
What is the best way of achieving the essential termination of an object if a load fails? Should I load data into the object from outside the constructor? Is there some sort of design pattern that I should look into?
Any help would be appreciated.
function __construct($camper_id){
try{
$query = "SELECT * FROM campers WHERE camper_id = $camper_id";
$getResults = mysql_query($query);
$records = mysql_num_rows($getResults);
if ($records != 1) {
throw new Exception('Camper ID not Found.');
}
while($row = mysql_fetch_array($getResults))
{
$this->camper_id = $row['camper_id'];
$this->first_name = $row['first_name'];
$this->last_name = $row['last_name'];
$this->grade = $row['grade'];
$this->camper_age = $row['camper_age'];
$this->camper_gender = $row['gender'];
$this->return_camper = $row['return_camper'];
}
}
catch(Exception $e){
return false;
}
}
A constructor in PHP will always return void. This
public function __construct()
{
return FALSE;
}
will not work. Throwing an Exception in the constructor
public function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
would terminate script execution unless you catch it somewhere
try {
$camper = new SummerCamper(1);
} catch(Exception $e) {
$camper = FALSE;
}
You could move the above code into a static method of SummerCamper to create instances of it instead of using the new keyword (which is common in Java I heard)
class SummerCamper
{
protected function __construct($camperId)
{
if($camperId === 1) {
throw new Exception('ID 1 is not in database');
}
}
public static function create($camperId)
{
$camper = FALSE;
try {
$camper = new self($camperId);
} catch(Exception $e) {
// uncomment if you want PHP to raise a Notice about it
// trigger_error($e->getMessage(), E_USER_NOTICE);
}
return $camper;
}
}
This way you could do
$camper = SummerCamper::create(1);
and get FALSE in $camper when the $camper_id does not exist. Since statics are considered harmful, you might want to use a Factory instead.
Another option would be to decouple the database access from the SummerCamper altogether. Basically, SummerCamper is an Entity that should only be concerned about SummerCamper things. If you give it knowledge how to persist itself, you are effectively creating an ActiveRecord or RowDataGateway. You could go with a DataMapper approach:
class SummerCamperMapper
{
public function findById($id)
{
$camper = FALSE;
$data = $this->dbAdapter->query('SELECT id, name FROM campers where ?', $id);
if($data) {
$camper = new SummerCamper($data);
}
return $camper;
}
}
and your Entity
class SummerCamper
{
protected $id;
public function __construct(array $data)
{
$this->id = data['id'];
// other assignments
}
}
DataMapper is somewhat more complicated but it gives you decoupled code which is more maintainable and flexible in the end. Have a look around SO, there is a number of questions on these topics.
To add to the others' answers, keep in mind that you can throw different types of exceptions from a single method and handle them each differently:
try {
$camper = new SummerCamper($camper_id);
} catch (NoRecordsException $e) {
// handle no records
} catch (InvalidDataException $e) {
// handle invalid data
}
Throwing an exception from the constructor is probably the right approach. You can catch this in an appropriate place, and take the necessary action (e.g. display an error page). Since you didn't show any code, it's not clear where you were catching your exception or why that didn't seem to work.
try {
$camper = new SummerCamper($id);
$camper->display();
} catch (NonexistentCamper $ex) {
handleFailure($ex);
}

PHP Try and Catch for SQL Insert

I have a page on my website (high traffic) that does an insert on every page load.
I am curious of the fastest and safest way to (catch an error) and continue if the system is not able to do the insert into MySQL. Should I use try/catch or die or something else. I want to make sure the insert happens but if for some reason it can't I want the page to continue to load anyway.
...
$db = mysql_select_db('mobile', $conn);
mysql_query("INSERT INTO redirects SET ua_string = '$ua_string'") or die('Error #10');
mysql_close($conn);
...
Checking the documentation shows that its returns false on an error. So use the return status rather than or die(). It will return false if it fails, which you can log (or whatever you want to do) and then continue.
$rv = mysql_query("INSERT INTO redirects SET ua_string = '$ua_string'");
if ( $rv === false ){
//handle the error here
}
//page continues loading
This can do the trick,
function createLog($data){
$file = "Your path/incompletejobs.txt";
$fh = fopen($file, 'a') or die("can't open file");
fwrite($fh,$data);
fclose($fh);
}
$qry="INSERT INTO redirects SET ua_string = '$ua_string'"
$result=mysql_query($qry);
if(!$result){
createLog(mysql_error());
}
You can implement throwing exceptions on mysql query fail on your own. What you need is to write a wrapper for mysql_query function, e.g.:
// user defined. corresponding MySQL errno for duplicate key entry
const MYSQL_DUPLICATE_KEY_ENTRY = 1022;
// user defined MySQL exceptions
class MySQLException extends Exception {}
class MySQLDuplicateKeyException extends MySQLException {}
function my_mysql_query($query, $conn=false) {
$res = mysql_query($query, $conn);
if (!$res) {
$errno = mysql_errno($conn);
$error = mysql_error($conn);
switch ($errno) {
case MYSQL_DUPLICATE_KEY_ENTRY:
throw new MySQLDuplicateKeyException($error, $errno);
break;
default:
throw MySQLException($error, $errno);
break;
}
}
// ...
// doing something
// ...
if ($something_is_wrong) {
throw new Exception("Logic exception while performing query result processing");
}
}
try {
mysql_query("INSERT INTO redirects SET ua_string = '$ua_string'")
}
catch (MySQLDuplicateKeyException $e) {
// duplicate entry exception
$e->getMessage();
}
catch (MySQLException $e) {
// other mysql exception (not duplicate key entry)
$e->getMessage();
}
catch (Exception $e) {
// not a MySQL exception
$e->getMessage();
}
if you want to log the error etc you should use try/catch, if you dont; just put # before mysql_query
edit :
you can use try catch like this; so you can log the error and let the page continue to load
function throw_ex($er){
throw new Exception($er);
}
try {
mysql_connect(localhost,'user','pass');
mysql_select_db('test');
$q = mysql_query('select * from asdasda') or throw_ex(mysql_error());
}
catch(exception $e) {
echo "ex: ".$e;
}
Elaborating on yasaluyari's answer I would stick with something like this:
We can just modify our mysql_query as follows:
function mysql_catchquery($query,$emsg='Error submitting the query'){
if ($result=mysql_query($query)) return $result;
else throw new Exception($emsg);
}
Now we can simply use it like this, some good example:
try {
mysql_catchquery('CREATE TEMPORARY TABLE a (ID int(6))');
mysql_catchquery('insert into a values(666),(418),(93)');
mysql_catchquery('insert into b(ID, name) select a.ID, c.name from a join c on a.ID=c.ID');
$result=mysql_catchquery('select * from d where ID=7777777');
while ($tmp=mysql_fetch_assoc($result)) { ... }
} catch (Exception $e) {
echo $e->getMessage();
}
Note how beautiful it is. Whenever any of the qq fails we gtfo with our errors. And you can also note that we don't need now to store the state of the writing queries into a $result variable for verification, because our function now handles it by itself. And the same way it handles the selects, it just assigns the result to a variable as does the normal function, yet handles the errors within itself.
Also note, we don't need to show the actual errors since they bear huge security risk, especially so with this outdated extension. That is why our default will be just fine most of the time. Yet, if we do want to notify the user for some particular query error, we can always pass the second parameter to display our custom error message.
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
I am not sure if there is a mysql version of this but adding this line of code allows throwing mysqli_sql_exception.
I know, passed a lot of time and the question is already checked answered but I got a different answer and it may be helpful.
$sql = "INSERT INTO customer(FIELDS)VALUES(VALUES)";
mysql_query($sql);
if (mysql_errno())
{
echo "<script>alert('License already registered');location.replace('customerform.html');</script>";
}
To catch specific error in Mysqli
$conn = ...;
$q = "INSERT INTO redirects (ua_string) VALUES ('$ua_string')";
if (mysqli_query($conn, $q)) {
// Successful
}
else {
die('Mysqli Error: '.$conn->error); // Show Error Complete Description
}
mysqli_close($conn);
Use any method described in the previous post to somehow catch the mysql error.
Most common is:
$res = mysql_query('bla');
if ($res===false) {
//error
die();
}
//normal page
This would also work:
function error() {
//error
die()
}
$res = mysql_query('bla') or error();
//normal page
try { ... } catch {Exception $e) { .... } will not work!
Note: Not directly related to you question but I think it would much more better if you display something usefull to the user. I would never revisit a website that just displays a blank screen or any mysterious error message.
$new_user = new User($user);
$mapper = $this->spot->mapper("App\User");
try{
$id = $mapper->save($new_user);
}catch(Exception $exception){
$data["error"] = true;
$data["message"] = "Error while insertion. Erron in the query";
$data["data"] = $exception->getMessage();
return $response->withStatus(409)
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
if error occurs, you will get something like this->
{
"error": true,
"message": "Error while insertion. Erron in the query",
"data": "An exception occurred while executing 'INSERT INTO \"user\" (...) VALUES (...)' with params [...]:\n\nSQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for integer: \"default\"" }
with status code:409.

Categories