PHP: catch exception in cascade. Is it possible? - php

I really need your help...
How can I catch exception in cascade?
I have a function ( A ) that call another function in another file ( B ) that call another function in another file ( C ).
How can I get in the catch of the function A, the errors of the function C?
Is it possible?
Look my example below... I tried to be the most clear possible...
// This code is in a file ( file A ) that call a function of a file B
require_once('/User_Login.php');
try
{
CREATE_NEW_User_Login(arr_Test, 'hello');
return true;
}
catch(Exception $e)
{
echo $e->getMessage();
return false;
}
}
// This code is in another file ( file B ) that call a function of a file C
public function CREATE_NEW_User_Login($_INPUT_Array = null, $_INPUT__Password = null)
{
// Db connection...
require_once('/User__DBclass.php');
$DBCl_User = new User($DBConn, null);
$DBCl_User->SET__CLASS_ATTRIBUTES_Value__by_Array($_INPUT_Array);
$DBCl_User->SETTER__user_pass(md5($_INPUT__Password));
$this->config['mysqli']->beginTransaction();
try
{
$DBCl_User->INSERT__NEW_ROW())
return true;
}
catch(Exception $e)
{
echo $e->getMessage();
return false;
}
}
// This code is in another file ( file C )
// In the User Class ( file: User__DBclass.php )
public function INSERT__NEW_ROW()
{
$this->config['stmt'] = $this->config['mysqli']->prepare('INSERT INTO tbl_user
SET user_id = :user_id,
user_name = :user_name,
act_code = :act_code;');
$this->config['stmt']->bindParam(':user_id', $this->user_id, PDO::PARAM_INT);
$this->config['stmt']->bindParam(':user_name', $this->user_name, PDO::PARAM_STR);
$this->config['stmt']->bindParam(':act_code', $this->act_code, PDO::PARAM_STR);
try
{
$this->config['stmt']->execute();
$this->user_id = intval($this->config['mysqli']->lastInsertId());
return $this->user_id;
}
catch(PDOException $e)
{
echo $e->getMessage();
return false;
}
}

If you want to cascade the exceptions, then you need to throw them again instead of just returning false.
You can either create a new exception type for each of your exception, or your can create a general exception and set the appropriate message and code.
catch(Exception $e)
{
//do something here if you want
throw $e;
}
Reference : http://php.net/manual/en/language.exceptions.php

Just don't catch the exception in the function where it is thrown. Then it'll be caught in your calling function.

This is the basic comportment of Exception : if you don't catch directly an exception this one is thrown to the upper element and so on to the "Root script".
Attention, you must catch the Exception, otherwise you'll get an error.
You can also "re"throwing an Exception from a catch block, like this:
catch(PDOException $e) {
throw $e;
}

Related

How to manage exceptions in multiple nested functions?

I am learning how to use Exceptions in PHP. In a subfunction of my code, I want to throw an Exception to stop the main function if an error appears.
I have three functions:
function main_buildt_html(){
...
check_if_parameters_are_ok();
// if the subfunction_check exception is thrown, don't execute the process below and go to an error page
...
}
function check_if_parameters_are_ok(){
...
try{
...
subfunction_check();
...
}catch(Exception $e){
}
...
}
function subfunction_check(){
...
if ($some_error) throw new Exception("Its not ok ! stop the process and redirect the user to an error page");
...
}
From my main "main_buildt_html" function, how can I properly detect if an exception has been thrown?
I want to detect the "subfunction" exception from the main function so I can stop the standard process and redirect the user to an error HTML page.
Normally the exception will be throwin up until the highest level in the chain, or when you catch it in any level.
in your case, if you want to catch the exception in check_if_parameters_are_ok() and main_buildt_html() functions, you need to throw the exception up in the check_if_parameters_are_ok() function.
function check_if_parameters_are_ok(){
...
try{
...
subfunction_check();
...
}catch(Exception $e){
//handle exception.
throw $e; // throw the excption again
}
}
now you need to catch the excption in the main_buildt_html() function.
function main_buildt_html(){
try {
check_if_parameters_are_ok();
} catch (Exception $e) {
// handle the excption
}
}
check_if_parameters_are_ok() should return false when it catches the error. The main function should test the value.
function main_buildt_html(){
...
if (check_if_parameters_are_ok()) {
...
} else {
...
}
}
function check_if_parameters_are_ok(){
...
try{
...
subfunction_check();
...
}catch(Exception $e){
return false;
}
...
}

How do I catch more than one exception type?

I have the following code -
public function getPosts($limit = 25, $author = null) {
try {
} catch(\GuzzleHttp\Exception\ClientException $e) {
return [];
}
return [];
}
I added this b/c when the page 404s I get the ClientException, however if the server returns a 500 error I get a ServerException - I tried just replacing this with catch(Exception $ex), but I still get the unhandled/uncaught exception error.
You can have multiple catch blocks for different exception types in php:
try {
} catch(\GuzzleHttp\Exception\ClientException $e) {
return [];
} catch(\GuzzleHttp\Exception\ServerException $e) {
//handle it...
}
However, the assuming the Guzzle exceptions extend the general php Exception class (which of course they do), changing it to just Exception $e should work. Are you sure this is where the exception is being thrown?
On the guzzle site http://docs.guzzlephp.org/en/latest/quickstart.html#exceptions you can see GuzzleHttp\Exception\TransferException base for all the client/server extensions, so you could just try to catch a GuzzleHttp\Exception\TransferException;
just list multiple catches:
try {
...
} catch (FooException e) {
...
} catch (BarException e) {
...
} catch (Exception e) {
...
}
Just keep catching
public function getPosts($limit = 25, $author = null) {
try {
} catch(\GuzzleHttp\Exception\ClientException $e) {
return [];
} catch (ServerException) {
//some code
}
return [];
}
be aware of namespaces, if you try to catch Exception it will catch all exceptions, if it did not, maybe you didnt include the exception as use Exception or tried to catch \Exception
The catch can be chained one after the other for handling multiple Error Types.
public function getPosts($limit = 25, $author = null) {
try {
} catch(\GuzzleHttp\Exception\ClientException $e) {
return [];
} catch(\GuzzleHttp\Exception\ServerException $e) {
return [];
}
return [];
}

Handling an exception, and only executing code if an exception was not thrown

My script_a.php:
try {
Class1::tryThis();
}
catch (Exception $e) {
// Do stuff here to show the user an error occurred
}
Class1::tryThis() has something like:
public function tryThis() {
Class2::tryThat();
self::logSuccessfulEvent();
}
The problem is that Class2::tryThat() can throw an exception.
If it does throw an exception, it seems that the line self::logSuccessfulEvent(); still gets executed.
How can I refactor this code so that self::logSuccessfulEvent() only occurs when an exception is not thrown, yet at the same time letting script_a.php know when an exception has been thrown?
This function will return whether or not the operation was successful (true = success, false = failure)
public function tryThis() {
$success = true;
try {
Class2::tryThat();
self::logSuccessfulEvent();
} catch( Exception $e) {
$success = false;
}
return $success;
}
What you're describing does not seem to be the case.
Code:
<?php
class Class1 {
public function tryThis() {
echo "Class1::tryThis() was called.\n";
Class2::tryThat();
self::logSuccessfulEvent();
}
public function logSuccessfulEvent() {
echo "Class1::logSuccessfulEvent() was called.\n";
}
}
class Class2 {
public function tryThat() {
echo "Class2::tryThat() was called.\n";
throw new Exception('Exception generated in Class2::tryThat()');
}
}
try {
Class1::tryThis();
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
Output:
Class1::tryThis() was called.
Class2::tryThat() was called.
Exception generated in Class2::tryThat()
As you can see, the Class1::logSuccessfulEvent() method is never executed when an exception is generated in Class2::tryThat(), and it shouldn't (won't) either. Exceptions bubble up until they are caught or produce a fatal error. Once an exception is caught, control of the program returns to the code after the catch block. In this particular case, that would mean that control of the program never reaches the logging method.

How to change exception message of Exception object?

So I catch an exception (instance of Exception class) and what I want to do is change its exception message.
I can get the exception message like this:
$e->getMessage();
But how to set an exception message? This won't work:
$e->setMessage('hello');
For almost every single case under the sun, you should throw a new Exception with the old Exception attached.
try {
dodgyCode();
}
catch(\Exception $oldException) {
throw new MyException('My extra information', 0, $oldException);
}
Every once in a while though, you do actually need to manipulate an Exception in place, because throwing another Exception isn't actually what you want to do.
A good example of this is in Behat FeatureContext when you want to append additional information in an #AfterStep method. After a step has failed, you may wish to take a screenshot, and then add a message to the output as to where that screenshot can be seen.
So in order to change the message of an Exception where you can just replace it, and you can't throw a new Exception, you can use reflection to brute force the parameters value:
$message = " - My appended message";
$reflectionObject = new \ReflectionObject($exception);
$reflectionObjectProp = $reflectionObject->getProperty('message');
$reflectionObjectProp->setAccessible(true);
$reflectionObjectProp->setValue($exception, $exception->getMessage() . $message);
Here's that example the Behat in context:
/**
* Save screen shot on failure
* #AfterStep
* #param AfterStepScope $scope
*/
public function saveScreenShot(AfterStepScope $scope) {
if (!$scope->getTestResult()->isPassed()) {
try {
$screenshot = $this->getSession()->getScreenshot();
if($screenshot) {
$filename = $this->makeFilenameSafe(
date('YmdHis')."_{$scope->getStep()->getText()}"
);
$filename = "{$filename}.png";
$this->saveReport(
$filename,
$screenshot
);
$result = $scope->getTestResult();
if($result instanceof ExceptionResult && $result->hasException()) {
$exception = $result->getException();
$message = "\nScreenshot saved to {$this->getReportLocation($filename)}";
$reflectionObject = new \ReflectionObject($exception);
$reflectionObjectProp = $reflectionObject->getProperty('message');
$reflectionObjectProp->setAccessible(true);
$reflectionObjectProp->setValue($exception, $exception->getMessage() . $message);
}
}
}
catch(UnsupportedDriverActionException $e) {
// Overly specific catch
// Do nothing
}
}
}
Again, you should never do this if you can avoid it.
Source: My old boss
Just do this, it works I tested it.
<?php
class Exception2 extends Exception{
public function setMessage($message){
$this->message = $message;
}
}
$error = new Exception2('blah');
$error->setMessage('changed');
throw $error;
You can't change Exception message.
You can however determine it's class name and code, and throw a new one, of the same class, with same code, but with different message.
You can extend Exception and use the parent::__construct to set your message. This gets around the fact that you cannot override getMessage().
class MyException extends Exception {
function __construct() {
parent::__construct("something failed or malfunctioned.");
}
}
here a generified snippet i'm using.
foreach ($loop as $key => $value)
{
// foo($value);
thow new Special_Exception('error found')
}
catch (Exception $e)
{
$exception_type = get_class($e);
throw new $exception_type("error in $key :: " . $e->getMessage());
}
An ugly hack if you don't know which kind of exception you're handling (that can have its own properties) is to use reflection.
try {
// business code
} catch (\Exception $exception) {
$reflectedObject = new \ReflectionClass(get_class($exception));
$property = $reflectedObject->getProperty('message');
$property->setAccessible(true);
$property->setValue($exception, "new message");
$property->setAccessible(false);
throw $exception;
}
You should use this crap wisely in very specific case when you don't have any other choice.
You can't change the message given by the Exception class. If you wanted a custom message, you would need to check the error code using $e->getCode() and create your own message.
If you really wanted to do this (in the only situation I can think that you might want to do it), you could re-throw the exception:
function throwException() {
throw new Exception( 'Original' );
}
function rethrowException() {
try {
throwException();
} catch( Exception $e ) {
throw new Exception( 'Rethrow - ' . $e->getMessage() );
}
}
try {
rethrowException();
} catch( Exception $e ) {
echo $e->getMessage();
}
The php Exception class has a __toString() method which is the only method within the Exception class that is not final, meaning it can be customised.
class HelloMessage extends Exception {
function __toString() {
return $this->getMessage()." you have an error with code: ".$this->getCode();
}
}
You use it as follows within try-catch block:
try {
if (2 > 0) {
throw new HelloMessage("Hello", 10);
}
} catch (HelloMessage $e) {
echo $e;
}
Output would be:
Hello you have an error with code: 10
You can extend Exception with your own, and put a setter in it
class MyException extends Exception
{
private $myMessage = '';
public function getMessage()
{
if ($this->myMessage === '') {
return parent::getMessage();
} else {
return $this->myMessage;
}
public function setMessage($msg)
{
$this->myMessage = $msg;
}
}
This is an improved version of David Chan's answer. It's a re-throw solution which uses get_class to rethrow the same exception type, and it passes all parameters to the constructor, even in the case of ErrorException, which has six rather than three constructor parameters.
foreach ($loopvar as $key => $value)
{
doSomethingThatMightThrow($value);
}
catch (\Exception $e)
{
$exception_type = get_class($e);
$new_message = "[key '" . $key . "'] " . $e->getMessage();
if ($e instanceof \ErrorException) {
throw new $exception_type($new_message, $e->getCode(), $e->getSeverity(), $e->getFile(), $e->getLine(), $e);
}
throw new $exception_type($new_message, $e->getCode(), $e);
}

catch exception in a fashionable way

I have an extended class for dateTime who makes some extra validation steps.
When a given date is invalid, it throws an exception.
Now I have some MySQL records with dates in zero (0000-00-00 00:00:00). In those cases, I want to show the text "never", so I have to catch the exception, and now I have this horrible mess...
try
{
$sellDate = new Date();
$sellDate ->setFromMySQL($this->_data['lastSell']);
$sellDateDMY = $dateSell->getDMY(TRUE);
}
catch (Exception $e)
{
if($e->getMessage() == 'Invalid date.')
$sellDateDMY = 'Never';
else
throw new Exception($e->getMessage());
}
$info[] = array('desc' => 'Last Sell: ' , 'data' => $sellDateDMY);
Any better way to do this?
Depends on which method it is that throws. The simplest would be to subclass Date again (maybe as NullableDate?) and override that method to not throw. The getDMY method would then return null, at which point you can display Never using the ternary operator ?:.
This way you won't have to use the ugly try/catch, and the intent of the code will also be clear to anyone who reads it for info on validation requirements -- by instantiating a NullableDate you definitely don't mind if its value is empty.
class DateException extends Exception {
public function __construct(Exception $e) {
if($e->getMessage() == 'Invalid date.') {
$this->message = 'Never';
} else {
$this->message = $e->getMessage();
}
}
}
try
{
$sellDate = new Date();
$sellDate ->setFromMySQL($this->_data['lastSell']);
$sellDateDMY = $dateSell->getDMY(TRUE);
}
catch (Exception $e)
{
throw new DateException($e);
}
You could start throwing different type of exceptions. Specific to the problem. Instead of the generic catch, you could do this
catch (DateInvalidException $de) {
//code
} catch (DateSomeOtherException $dso) {
//code
} catch (Exception $e) {
//general
}
But that's not a good solution. You're mixing up program exceptions and error validations.
Make your own Exception class for your Date functions.
class MyOwnDateException extends Exception {
... // Do something or probably nothing
}
And call it in your code:
try {
if($someErrorYouWantToCatch) {
throw new MyOwnDateException("error message", 100 /* Error code = optional */);
}
} catch(MyOwnDateException $mode) {
$sellDateDMY = 'Never';
}

Categories