I have to develop an exception handler that should handle like 5 different type of exceptions. Let's call them simply Ex1, Ex2, Ex3...
I though of doing a single class called ExHandler which will be instantiated like this:
...
} catch (Ex1 $e) { $h = new ExHandler($e); $h->render(); }
catch (Ex2 $e) { $h = new ExHandler($e); $h->render(); }
catch (Ex3 $e) { $h = new ExHandler($e); $h->render(); }
...
And inside ExHandler manage each different Exception differently using $e instance of Ex1, $e instance of Ex2, $e instance of Ex3...
But It doesn't seems a very good practice to me. Is it good? Is there any other way of doing this?
Should I create an Ex1Handler, Ex2Handler, Ex3Handler...? My S.O.L.I.D spirit tells me something is just wrong here. What is it?
I need to note before I answer this, that procedural programmers will look at this and think it's dumb :) but I can live with that, this is assuming an OOP application with HTML templating that outputs after the output_buffer is cleaned.
I always create a try/catch block encompassing the majority of my code in one call usually at the point where I start requiring other files as well as starting an output_buffer whilst in development.
ob_start();
try {
switch($appPage) {
case('work'):
require_once('im_bored_at_work.php');
break;
case('home'):
require_once('im_a_little_less_bored_at_home.php');
break;
default:
require_once('on_the_fence.php');
}
} catch (Exception $e) {
// Handle exception caught and apply formatting
}
$devOut = ob_get_contents();
ob_end_flush();
To give an example how I would handle the multiple exceptions you need to catch with a custom class
class CustomExceptionHandler extends Exception {
private $msg;
private $code;
private $otherVars;
public function __construct($msg,$code=0,$otherDebugVar=null){
$this->msg = $msg != null ? $msg : "An unknown exception was thrown";
$this->code = $code;
$this->otherVars = $otherDebugVar;
parent::__construct($msg,$code);
}
public function getOtherVars() {
return $this->otherVars;
}
}
The idea is to just keep the custom information within the exception object, and when you rethrow the exception at the end of a try/catch block as a standard exception you include the formatted custom message, it shouldn't really matter now which Exception handler picked up the original exception as all the info you will need will come downstream and be caught in the original try / catch block.
class BasicTemplate {
private $template;
private $path;
private $contents;
public function __construct($template, $path) {
$this->template = $template;
$this->path = $path;
$this->buildTemplate();
}
private function buildTemplate() {
if ($contents = #file_get_contents($this->path . $this->template)) {
$this->contents = $contents;
} else {
$e = new CustomExceptionHandler("Message",2,$this->path . $this->template);
// Do whatever else you want to do with custom exception handling class
throw $e;
}
}
}
Now you need to catch your exception and rethrow it:
try {
$html = new BasicTemplate($temp,$path);
} catch {CustomExceptionHandler $e) {
throw new Exception("Message: {$e->getMessage()} Other Info: {$e->getOtherVars()}",$e->getCode());
}
That's the rough idea anyhow, hope it helps.
Related
I currently have the following PHP which uses a try/catch block checking for exceptions.
try {
$obj->login('User', 'Pass');
$obj->joinServer('Server');
} catch(ConnectionException $objException){
die();
}
...and this is the ConnectionException class:
class ConnectionException extends Exception {
public function __construct($strError, $intError, $mixOther = null){
$this->strError = $strError;
$this->intError = $intError;
echo $this->intError, ' - ', $this->strError, chr(10);
}
}
Now let's say if the "catch" part returns a specific error (if ConnectionException outputs a specific message), how can I "retry" the try/catch again?
I guess you can nest the try/catch, wrap it the code into a function to avoid code repetition:
function login($obj){
$obj->login('User', 'Pass');
$obj->joinServer('Server');
}
try {
login($obj);
} catch(ConnectionException $objException){
try {
login($obj);
} catch(ConnectionException $objException){
die();
}
}
If you want to do this at this level, instead of having it throw a ConnectionException every time, throw a different exception that's a subclass of ConnectionException that signifies when you would want to rerun the logic. Then, encapsulate the logic that you have within the try to a function, and call it again.
try {
doWork( $obj);
} catch( ConnectionTimeoutException $e) {
// Wait a bit, then retry
try {
sleep(10);
doWork( $obj);
} catch( ConnectionException $e) {
// Because of the subclass, this would catch both ConnectionException and ConnectionTimeoutException
die();
}
} catch( ConnectionException $e) {
die();
}
You can also change how you're calling this function, and call it twice depending on the result of ConnectionException. Although, in that manner, it may make more sense to base this decision on a return value instead of an Exception.
My suggestion would be to create a simple while loop that checks for a condition:
try_connection = true;
while(try_connection) {
try {
$obj->login('User', 'Pass');
$obj->joinServer('Server');
} catch(ConnectionException $objException){
if(!specific_message)
try_connection = false;
}
}
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);
}
I am trying to understand what the best approach would be to handle Exceptions in the following scenario:
I have a class employee:
class employee extends person {
private $salary;
private $baseSalary = 6.5;
function __construct($f, $m, $l, $a,$fsalary=0){
if(!is_numeric($fsalary)){
throw new Exception("Age supplied is not a number", 114);
}
parent::__construct($f, $m, $l, $a);
$this->salary=$fsalary;
}
function GetDetails(){
return parent::GetName().
"<br/>".
$this->salary;
}
function __toString(){
return $this->GetDetails();
}
}
And using this:
try{
if(!$f = new employee("Sarah", "Sebastian", "Pira", "abc")){
throw new Exception();
}
else {
echo $f;
}
}
catch (Exception $e){
echo "<br/>";
echo var_dump($e);
}
Now I would think it would be a good idea to throw an exception in the class and then use just one catch block in all the scripts that would be using an employee object - But this doesn't seem to work - I need to have a try catch block within the class - Is this the correct way of looking at this?
Thanks
I think what you're saying is that you want to do something like this:
try {
class Employee extends Person {
// ...blah blah...
}
}
catch(Exception $e) {
// handle exception
}
...and then be able to insantiate it in other classes, without explicitly catching any exceptions:
// try { << this would be removed
$employee = new Employee();
// }
// catch(Exception $e) {
// (a whole bunch of code to handle the exception here)
// }
You can't do that, because then the try/catch block in the class will only catch any exceptions that occur when defining the class. They won't be caught when you try to instantiate it because your new Employee line is outside the try/catch block.
So really, your problem is that you want to be able to re-use a try/catch block in multiple places without re-writing the code. In that case, your best solution is to move the contents of the catch block out to a separate function that you can call as necessary. Define the function in the Employee class file and call it like this:
try {
$employee = new Employee();
$employee->doSomeStuff();
$employee->doMoreStuffThatCouldThrowExceptions();
}
catch(Exception $e) {
handle_employee_exception($e);
}
It doesn't get rid of the try/catch block in every file, but it does mean that you don't have to duplicate the implementation of the exception-handling all the time. And don't define handle_employee_exception as an instance method of the class, do it as a separate function, otherwise it will cause a fatal error if the exception is thrown in the constructor because the variable won't exist.
You should read more about Exceptions in PHP.
You can handle exceptions within the methods of the class, sure. But you should rethink how you want to do this and... why.
Good practice is also creating own exception class, so you are able to distinguish exceptions thrown by your module / class from the exceptions thrown by something else. It looks like that (see more):
class EmployeeModule_Exception extends Exception {}
and when it comes to throwing exception:
// the second parameter below is error code
throw new EmployeeModule_Exception('some message', 123);
Catching is similar, only the below example will catch only your module's exceptions:
try {
// some code here
} catch (EmployeeModule_Exception $e) {
// display information about exception caught
echo 'Error message: ' . $e->getMessage() . '<br />';
echo 'Error code: ' . $e->getCode();
}
What are best way of error handling? This is what I came up with:
class test {
public static function Payment($orderid, $total) {
if (empty($orderid) && empty($total)) {
return array('status' => 'fail', 'error' => 'Missing Data');
}
}
}
I heard about Try/Exceptions but how to fit that into my code? If you could provide example that would be great!
If you use PHP 5, you can handle error with exception :
http://fr2.php.net/manual/en/class.exception.php
This way is cleaner than manual set exception message, because you have access to a try catch system and you can isolate exception handling
As mentioned, use Exceptions. Specific to your example, you throw an exception if some condition fails. Then when you envoke the method that can throw an exception, you wrap it with a try/catch handling block.
class test {
public static function Payment( $orderid, $total ) {
if (empty( $orderid ) && empty( $total )) {
throw new Exception('Missing Data');
}
}
}
try {
test::Payment("1", "2"); //should be fine
test::Payment(); //should throw exception
} catch (Exception $e){
echo $e;
//do other things if you need
}
You could use exceptions.
However, in the use case you've posted, simply doing the checks at the controller level should suffice.
I also think that explicitly checking the return type for array (on fail) is counter intuitive.
Here is how you might modify your code to use an exception. It also helps to document the circumstances under which the exception is thrown.
class test {
/**
* [method description]
* #throws Exception if the order ID or total is empty
*/
public static function Payment($orderid, $total) {
if (empty($orderid) && empty($total)) {
throw new Exception("fail: Missing Data");
}
}
}
You can also create your own exception class if you want to include extra data in the exception.
class MyException extends Exception{
public $status, $error;
public function __construct($status, $error){
parent::__construct("$status: $error");
$this->status = $status;
$this->error = $error;
}
}
I tend to lean towards throwing exceptions, and then using the try/catch mechanism to deal with the aftermath. The man page is here: http://php.net/manual/en/language.exceptions.php
The best practice is to use Exceptions.
http://php.net/manual/en/language.exceptions.php
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';
}