Cleaning up code for a custom exception class in PHP - php

I'm playing around with custom PHP exceptions for the first time, and would like some help with cleaning up some code. In particular, I'm catching PDO errors, and have written a class to mail myself a stack trace of the errors. The way that I'm currently doing things is as follows:
try {
//db stuff
} catch (PDOException $e) {
throw new My_Own_Exception($e);
exit;
}
where my My_Own_Exception does the job with:
class My_Own_Exception extends Exception
{
/*code to mail myself error info: this part works!
}
While the above works, I feel like I should be able to just write something cleaner such as:
try {
} catch (My_Own_Exception $e) {
exit;
}
where My_Own_Exception is an extension of the PDOException class. A few questions about this: First, is the second way the better approach? Second, if so, is it possible? Third, if it is possible, how do I let PHP know that My_Own_Exception "exists" if My_Own_Exception is never instantiated anywhere? Hopefully the third question makes some sense: my gut tells me that if I can make that happen, then my approach should be possible.

I don't think that an exception is the correct place for logic, it should contain information about the error. A PDOException is useful because you know it originates from your PDO code, if you throw a MyException instead, you need to at least give more (useful) information.
This being set, you should read BiVOC's comment on your original question.
If you have a custom exception handler, you can then differentiate via instanceof.
function exception_handler($exception) {
if ($exception instanceof PDOException) {
//mail
}
//always log/etc.
}

What you are trying to do wont work, because nothing in the PDO code will throw your exception, unfortunately. So you are going to have to throw it yourself, like you were in the first example.
Also, the exit in the first example will never be hit.

Related

Deliberately not catching Exceptions

So I have this little bit of code
public function getPrices($debtorId)
{
$priceListId = $this->getPriceListId($debtorId);
if(!$priceListId){
throw new \Exception('No list found for this customer');
}
// doing some operations here that require $priceListId
return $prices;
Up until now, I would have done something like
if(!$priceListId) exit('No list found for this customer');
The difference being, that I could catch the Exception if I want to, while that's not possible with the exit statement.
However, in this case I do want my program to exit. But my IDE warns me that I'm not catching Exceptions. So, should I really do this now:
try {
$prices = $priceHandler->getPrices($debtorId);
} catch(Exception $e) {
exit($e->getMessage());
}
The latter appears to me as unecessary and actually decreasing code quality. So: Is it acceptable to deliberately NOT catch some exceptions? Or should I even get rid of the exception alltogether and just use plain old exit?
I tried searching for this question, but I only got results about people who had technical problems with try/catch not working.
No, you should not catch your exception there if you do not want to.
Even if you want to catch it, it could very easily be somewhere else, so you definitely do not have to wrap this in a try-catch.
Instead, you should tell your IDE that this method is supposed to throw an exception:
/**
* #throws \Exception
*/
public function getPrices($debtorId)

PHPUnit try catch does not work in a test case

I tried to catch an exception inside a PHPUnit test but it does not work as I expected. The exception seems to be caught in a higher level and I do not understand why.
public function testException()
{
echo 'Enter';
try {
throw new Exception('error.');
} catch (Exception $e) {
echo 'catch Exception';
}
echo 'End';
}
In the output only 'Enter' is visible. I do not reach the 'End'.
(I am using PHP7)
Thanks
Edit:
Replacing all Exception with \Exception solved my issue
Handling exceptions can be tricky, especially when they are called Exception :-)
Just look at all the feedback this (almost the same) question has generated.
Now, the exception is not caught only if does not match to the expected class. In this case it is definitely because of non-precise Exception class names specification, which will most certainly be fixed by specifying them as \Exception.
As smartly put by a commenter from the mentioned question thread:
Without the backslash Exception is specific to the namespace and won't be matched (or caught).

Easy way to list all of exceptions "try" block may produce

Is there any easy way to find out, which types of exception I can expect in try block? Let's say I have:
<?php
try {
foo();
} catch (\A\B\FooException $e) {
} catch (\A\B\BarException $e) {
}
Is there any tool which can inspect the foo() code for me and list all of exceptions types I can expect there? So if there is \A\B\BazException I forgot, I can easily add another catch thanks to that list. For now I use search for "Exception", but sometimes there is to many the same results.
I don´t know any tools to do that. But you can create at least the code block below:
catch (Exception $e) {
mysql_query("INSERT INTO exceptions_tb ('exception_name','expection_message') VALUES ('"+$e->class+"', '"+addslashes($e->message)+"') excetption ", $conn);
}
And after consult the variations of exceptions happened in all your unit tests. Or simply you can do all possible ways and input data types in your unit tests and see what exceptions will arise.
I don´t believe there is a tool that can do this for you also because the tool will need to now all possible inputs and php variables are not predefined types.
...
catch (Exception $oError) {
Logger::logError($oError);
}
class Logger
{
public static function($oError){
// do here detection you like\want
// you can use loops and pre-def. instances of errors
if ($oError instanceof ExceptionCustom)
// some actions
}
...
}
}
class ExceptionCustom extends Exception {
// http://www.php.net/manual/en/language.exceptions.extending.php
//...
}
It's only sample, base thing. You can\should do it more complicated the way you want.
ADDED:
Main thing that you will decide, what error and what you should do, only after you receive exception, and of course you should create default action for it (no matter what exception is thrown).

Is there a way to catch an Exception without having to create a variable?

In PHP, I sometimes catch some exceptions with try/catch :
try {
...
} catch (Exception $e) {
// Nothing, this is a test that an exception is thrown.
}
With that kind of code, I end up with the variable $e that is created for nothing (lots of resources), and PHP_MD (PHP Mess Detector) creates a warning because of an unused variable.
Starting with PHP 8, it is possible to use a non-capturing catch.
This is the relevant RFC, which was voted favourably 48-1.
Now it will be possible to do something like this:
try {
readFile($file);
} catch (FileDoesNotExist) {
echo "File does not exist";
} catch (UnauthorizedAccess) {
echo "User does not have the appropriate permissions to access the file";
log("User attempted to access $file");
}
With this, for some edge cases where the exception details are not relevant and exception type already provides all the necessary context, it will be possible to catch the exception without creating a new variable.
You can with PHP 8 #see
PHP 5,7
No, but you can unset it.
try {
...
} catch (Exception $e) {
// Nothing, this is normal
unset($e);
}
If it is PHPMD causing this issue then you can suppress the warning.
PHPMD suppress-warnings
class Bar {
/**
* This will suppress UnusedLocalVariable
* warnings in this method
*
* #SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
public function foo() {
try {
...
} catch (Exception $e) {
// Nothing, this is normal
unset($e);
}
}
}
I'm assuming you are only catching the exception because you need to not because you want to.
With PHP 5,7 you have to use a catch if you want to use try and if you use a catch you have to declare a variable.
That's the whole point of exceptions - you can have multiple different catch blocks to catch any exceptions you'd want to handle. The exception's data has to be assigned somewhere, hence the variable. You could just do something like unset($e) inside the catch block if you really don't want to see those warnings... or disable the warnings (generally a bad idea).
I disagree fundamentally with Marc B's and Artefacto's answers. There are cases where ommitting the catch is better or even the only option. Especially when using external libraries (where you have no control over what exceptions are thrown) and/or async operations.
For example:
I want to create a file only if it doesn't exist yet. I'm using an external I/O library. Imagine it has File::exists($fileName) and File::create($fileName) methods.
Option 1 (if ommitting the catch was possible):
try {
File::create($fileName);
}
// Go on with the rest of the code.
Option 2 (without try/catch):
if (!File::exists($fileName))
File::create($fileName);
Here, option 1 is perfectly valid, since option 2 has two important issues:
If multiple threads are running and going through this code section at the same time, it could be that thread A first checks if the file exists. Next, thread B checks if the file exists. They both find that it doesn't exist. Thread A creates the file. Thread B then attempts to create it again and throws an exception even though you're using the if check.
It's very likely that the library itself already performs the !File::exists($fileName) check. Therefore you're wasting a call that is already made.
Note that if File::create throws other exceptions that might be unexpected it would be good to catch those.
Conclusion
Stating that something is never a good idea, is almost never a good idea. There are always exceptions (hehe) to the rule. Like any convention or design pattern, it's just a rule of thumb meant to help less experienced developers make the right decision.
No.
In any case, it's generally a bad idea to catch an exception and do nothing; exceptions exist precisely to force you to handle the exceptional circumstance (otherwise execution is aborted), so it's comprehensible the language doesn't facilitate such a use case.
As of PHP 8.0 it may be typed without variables, but the general case for each Exception is now Throwable. Class Exception implements Throwable.
try {
...
} catch (CustomException) {
// CustomException
} catch (Throwable) {
//All other classes implementing Throwable interface
}

Rethrowing exceptions under different names? What's the standard practice?

currently I have this client code in my PHP MVC web app:
try {
BookMapper::insert($book);
} catch (DbUniqueConstraintViolationException $e) {
$errorList->addMessage($book . " already exists!");
}
I'm wondering is it bad practice to refer to the low-level framework Db* exceptions to my client code? If so, should I adjust my model code like so:
class BookAlreadyExistsException extends Exception { }
class BookMapper {
public static function insert($book) {
try {
// call to DB-layer to insert $book
// (not relevant to the question)
} catch (DbUniqueConstraintViolationException $e) {
throw new BookAlreadyExistsException();
}
}
}
and then use this new client-code...
try {
BookMapper::insert($book);
} catch (BookAlreadyExistsException $e) {
$errorList->addMessage($book . " already exists!");
}
Or something else? Or is the original method fine?
Thanks!
EDIT: Just want to add, the latter method reads the best IMO, but it comes with the object creation / rethrowing overhead and more significantly, it requires duplicating the rethrowing code in every mapper's insert() method. The former method is easy to implement and catch and works for any model but I remember reading somewhere that you shouldn't do it this way?
I think you should definitly throw your own exception.
But I would also consider a third option and that is letting the insert method return true for success and false for failure. Exceptions should be used for exceptions and the fact that a book already exists might actually be an expected/predictable case.
And if duplicate books are truly excetions that should not be possible (unless for programming errors), then you could as well stick with the database exception but in that case don't catch it. Let it bubble all the way up.
I highly recommend this article. Although it is written for Java, the principles are quite applicable to PHP as well. It has good guidelines for what types of exceptions you should be throwing and catching.

Categories