I am working on a PHP project that requires validating a JSON request to a predefined schema, that is available in swagger. Now I have done my research and have found that the best project for this is SwaggerAssertions:
https://github.com/Maks3w/SwaggerAssertions
Within SwaggerAssertions/tests/PhpUnit/AssertsTraitTest.php, I would love to make use of the testAssertRequestBodyMatch method, where you do this:
self::assertRequestBodyMatch($request, $this->schemaManager, '/api/pets', 'post');
This assertion above does exactly what I need, but if I pass a invalid request it causes a fatal error. I want to trap this and handle the response back rather than the app quitting altogether.
How can I make use of this project, even though it looks like its all for PHPUnit? I am not too sure how one would make use of this project in normal PHP production code. Any help would be greatly appreciated.
Assertions throw exceptions if the condition is not met. If an exception is thrown, it will stop all following code from being executed until it's caught in a try catch block. Uncaught exceptions will cause a fatal error and the program will exit.
All you need to do to prevent your app from crashing, is handling the exception:
try {
self::assertRequestBodyMatch($request, $this->schemaManager, '/api/pets', 'post');
// Anything here will only be executed if the assertion passed
} catch (\Exception $e) {
// This will be executed if the assertion,
// or any other statement in the try block failed
// You should check the exception and handle it accordingly
if ($e instanceof \PHPUnit_Framework_ExpectationFailedException) {
// Do something if the assertion failed
}
// If you don't recognise the exception, re-throw it
throw $e;
}
Hope this helps.
Related
I am currently maintaining a application with a large code base. The application is still under development.
My goal is to log all the exceptions that are thrown in the application. Also the ones that are caught in the try catch block. Due to the large code base I cannot add single lines of code in the catch blocks or create my custom exception class.
The way I tried to solve it or look for a solution are:
Listen for class construction in the whole application
Override the Exception class (can't do this because it is an core php class)
My most recent code that I tried is:
My test exception (dashboardcontroller.php)
try {
throw new \Exception('custom thrown exception');
} catch (\Exception $e) {
Log::info('Exception caught');
}
Exceptions\Handler.php
public function report(Exception $exception)
{
Log::info($exception);
parent::report($exception);
}
Log
[2020-03-10 17:19:09] local.INFO: Exception caught
So the question is: How can I log / handle thrown exceptions that are caught in the try catch block without adding code that requires lot of changes?
Short answer: You can't (as far as I know that is).
Exceptions that are caught are, normally, caught for a reason; they are handled (or: they should be) in the catch-statement (i.e. exception handler). It's the uncaught exceptions that you want to log.
Exceptions should never be used for program-flow, but it does happen anyway and sometimes for good reason:
if (fileExists('foo.txt')) {
fileDelete('foo.txt'); // May result in a race-condition
}
V.s.:
try {
fileDelete('foo.txt')
catch (IOException) {
// NO-OP
}
Another common pattern:
while (true) {
try {
item = queue.Receive(10); // Wait max. 10 seconds
process(item); // Process item
} catch (TimeOutException) {
// Nothing on queue, handle other stuff and then continue waiting...
DoStuff();
}
}
Above patterns are quite common. Again: exceptions shouldn't be used for program-flow and the queue example above surely shouldn't exist but the reality is that it happens. Real world applications, libraries, API's etc. sometimes just work that way and as soon as you're dealing with 3rd party applications, libraries or API's you're in trouble because you can't always change everything to what you would like it to be.
Can you imagine your logfile in the above queue-example with your 'generic application wide catch logger' for an application running 24/7?
When to log an exception (and maybe just as important: what to log) should be considered on a case-by-case basis.
Sorry for this vague title, I didn't know how to title my question.
I'm listening on kernel.exception via the kernel.event_listener service. I use it in my API to catch all exceptions and serialize them in JSON for a clean error handling for the API customers.
I have to adapt the serialization depending on the exception types (my HTTP exceptions, Symfony HTTP exceptions, and others).
When a user is not authenticated when accessing a section restricted by access_control in security.yml, Symfony throws a non-HTTP Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException. In my serializer, a non-HTTP exception is converted in a 500 error. Since an InsufficientAuthenticationException is rather a 401 Unauthorized error, I have to catch this exception separately and convert it in my app-specific exception type.
Example:
# Find appropriate serialization
if($ex instanceof HttpErr\HttpErrorInterface) {
# AppBundle\Exceptions\Http\*
# A displayable error thrown intentionally by our controllers
$status = $ex->getStatusCode();
$message = $ex->getMessage();
$other = $ex->getAdditionalDatas();
} elseif($ex instanceof InsufficientAuthenticationException) {
throw new HttpErr\EUnauthorized; # look a this line
}
# more elseifs...
That works. The Symfony authentication exception is catched, then converted in EUnauthorized, and then EUnauthorized is serialized into JSON. But you can see that I throw the exception without message or previous exception.
Because I want to do this:
elseif($ex instanceof InsufficientAuthenticationException) {
# the standard argument $previous is in 2nd position in my exceptions instead of being 3rd.
# the previous argument is important for me since it will keep context in error propagation.
throw new HttpErr\EUnauthorized($ex->getMessage(), $ex);
}
When I do this (so, just adding two arguments), the serialization stops working, my event listener is not called and the app crashes (in prod, this will turn into a friendly WSoD):
Why?
In the first "if" you extract data for serialization, in the second you are just rethrowing a new exception.
This new exception does not go the kernel.exception flow anymore. It is correctly just thrown: as you can see you have the full stack of exceptions shown.
Ideally, you should end your onKernelException with some kind of Response.
EDIT
I'll expand a bit my previous answer with references to the Symfony documentation and code.
The HttpKernel docs say
If an exception is thrown at any point inside HttpKernel::handle, another event - kernel.exception is thrown. Internally, the body of the handle function is wrapped in a try-catch block. When any exception is thrown, the kernel.exception event is dispatched so that your system can somehow respond to the exception.
So, your listener is called after an exception in the handle function, but, as you can see in source no try/catch is provided by the handleException function. This basically means that an Exception thrown in your listener should not be caught.
In your listener you could swap the current exception with a new one with $event->setException(...) or just try to build a Response yourself.
In any case, throwing a new Exception does not seem the proper way here. I sadly don't know why you code works with or without parameters without all the code involved.
I don't know if it helps here, but I had similar problem my event listener is not called and the app crashes. So i worked around that and overrided one method in Kernel.php file like that:
protected function initializeContainer() {
try {
$container = parent::initializeContainer();
} catch (\Throwable $throwable){
// MY CATCH CODE GOES HERE
}
return $container;
}
You can also hook up to other Kernel methods and override them.
Notice: I'm using Symfony 4.2.*
I'm currently working in PHP. I'm working on a error system for my CMS I'm building (for the fun of it). For fatal errors in my system (not in the php compiler) I created a FatalException class that extends the built in Exception class. Since these types of errors kinda halt the system anyway, I threw in a exit into the __construct.
class FatalException extends Exception{
public function __construct($message) {
exit("<h1 style='color:red;' >FATAL ERROR: $message </h1>");
}
}
So in my code I'll check something like the connection to the database and if it can't then I'll just throw a FatalException("Can't connect to database: $database_error_message"). It won't be in a try/catch block.
When I run the code and can't connect to the database, for example, all I see on the screen is a sentence in big red letters. So it works just fine, but is this bad practice/coding?
EDIT:
Actually in truth it didn't start out this way. I was originally logging the error and then exiting in the catch area, but then I thought, if all fatal errors are going to exit anyway then just put in the in constructor. Then I noticed it wasn't actually getting to the catch area it was exiting. So putting the statement in a try/catch block was kind of a moot point. Which lead to the question.
If you're going to exit() unconditionally in a constructor, there's not really much point making it a constructor, let alone making the class an Exception. You could more simply (and honestly) have a static function called Fatal::Die($message).
The point of exceptions is that they describe what the error is (by having different classes for different exceptions) and can be caught - even if only to log them to a file and bail out of the program.
What if a particular page of your site could actually cope fine without a database connection (just missing the "latest news" or something)? Then it could catch( Database_Exception $e ) and continue, while the rest of your site just falls straight into the last-ditch "oh no something went wrong" message.
A message in big red letters is also not a great error-handling mechanism for anything that someone other than you will use - either they end up seeing technical details of the error when you're not looking, or you don't know what went wrong because you hid that error.
Even you wrap exit() into a member function of an exception, you are not using the exception here for error handling, but just the exit() - the exception is never thrown, PHP comes to halt before that happens.
So it works just fine, but is this bad practice/coding?
Yes, it is bad practice. It also works just fine if you would have created yourself a function:
function error_exit($message) {
exit("<h1 style='color:red;' >FATAL ERROR: $message </h1>");
}
Instead think about whether you want to use Excpetions or you want to exit().
This is a bad idea imho. An exception needs to be caught in order to function properly. You may however create a method like ->error( $index ); that attaches to every object you make.
From there you can route the error to a specific class with a try / catch block to properly handle the errors.
class TestClass
{
public function error( $index )
{
try
{
// Convert index to exception and throw it.
}
catch ( Exception $e )
{
// Handle the error
}
}
}
$a = new TestClass();
$a->error( 1000 );
Do note that this will not work for exceptions that php throws, you'll need to catch these seperately or work with execution hubs.
I'm using phpunit and the ecomdev module to unit test Magento. For a particular class the last code that needs to be tested is an exception on attempting to save a model in a try/catch. The only way I can see to get to the exception is alter the database temporarily. So I changed the table name which works, but then I have to name it back. If the test itself fails I'm in an inconsistent state.
I'd really like to get that code tested so the coverage is 100%. Otherwise I'll be wondering why it's 98% until I look at the code and remember the exception isn't tested.
I was trying to close the connection but I need it to log the exception. So I'm wondering if maybe there is something I can temporarily do to the model or resource model to cause the save to raise an exception. Something that would be reset on the next load.
One note - I can't see anyway that manipulating data will cause the exception. Again the only scenario I see causing an exception in production is if the database connection goes away.
Any ideas?
Edit:
Sample Code:
public function logStuff($stuff){
try{
Mage::getModel('my/stuff')
->setData('stuff', $stuff)
->save();
}catch(Exception $e){
Mage::helper('log/error')->logError(__METHOD__, "Could not save stuff: ". $e->getMessage());
}
}
To make test the exception catch, you need to replace your model instance with the mocked one.
It is very easy to achieve with EcomDev_PHPUnit extension:
$mockedStuff = $this->getModelMock('your/stuff', array('save'));
$mockedStuff->expects($this->once()) // How many times it is invoked
->method('save') // Wich method to mock
->will($this->returnCallback(function () {
throw new Exception('Some error text');
})); // Your exception
$this->replaceByMock('model', 'your/stuff', $mockedStuff);
Also you can evaluate your logging stuff by using mock as well.
$loggerMock = $this->getHelperMock('log/error', array('logError'));
$loggerMock->expects($this->once())
->method('logError')
->with('className::methodName', 'Could not save stuff: Some error text'); // Check that correct arguments passed
$this->replaceByMock('helper', 'log/error', $loggerMock);
Have fun with unit tests :)
Few days ago I deal with errors like this...
exit( 'Error!' );
or exit( 'Error!' );
Doh, right? =] Now I'm trying to learn Exceptions. This is how far I got...
http://pastie.org/1555970
Is that correct use of them? It would be cool that I can have more info about 'em. Like file where exception is thrown and line of that file. I know that there are build-in methods (in Exception class), but I want to somehow extend it so I don't need to write...
throw new My_Exception( '...' );
catch( My_Exception $e ) {
echo $e->my_method();
}
...but use old syntax.
throw new Exception( '...' );
catch( Exception $e ) {
echo $e->getMessage();
}
...or maybe you have any greater thought of Exceptions? How to deal with them? Help me! =]
In general you only need to echo/log exceptions, that cannot be otherwise handled. This pretty much means, that if you put your entire application within try block, there's only one place where you need to put echoing/logging logic (i.e. the catch block associated with the outermost try block).
If on the other hand the exception can be handled without stopping the application (in your example this could be providing a default numeric value, instead of incorrect value), then there's usually no need to echo/log it.
If you do want to log such exceptions (for debugging for example), then your application should contain a logging framework, so that it's enough to do in your catch blocks something like
} catch (Exception $e) {
ExceptionLogger::log($e); //static method == ugly, but it's for simplicity in this example
// do whatever needs to be done
}
log() method above would check if the logging is enabled, and if it is savenecessary data to a file.
Exceptions should be typed based upon the error that you find. The Spl Exceptions are a good start, but you really should be creating your own exceptions as well. Some common ones that I use:
FileNotFoundException extends RuntimeException <- self explanatory
HTTPException extends RuntimeException <- Used for http classes when a non-200 result is encountered
DatabaseQueryException extends LogicException <- Used for database query errors
Now, by typing them specifically, it lets you handle the errors in your code. So let's say that you want to fetch a HTTP resource. If that fails with anything but a 404, you want to try a backup URL. You could do that with:
try {
return getHttp($url1):
} catch (HttpException $e) {
if ($e->getCode() != 404) {
try {
return getHttp($url2);
} catch (HttpException $e2) {
//It's ok to ignore this, since why know it's an HTTP error and not something worse
return false;
}
} else {
return false;
}
}
As far as your example code that you posted, I would change a few things:
Change the thrown exception to InvalidArgumentException since it has more semantic meaning (I almost never throw a raw exception).
You should try to avoid catch(Exception $e) at all costs. You have no idea what exception was thrown, so how can you possibly handle it?
Only catch exceptions that you are reasonably sure you know how to handle (and outputting an error/logging is not handling, it's removing the usefulness of the exception). You should never see something like catch($e) { logerror($e); } or catch($e) { print $e->getMessage(); } since netiher is actually handling the exception.
If you don't fix or workaround the cause of the exception in your catch block, you should re-throw it. Let the code above you in the stack try to handle it. This is especially true with libraries and classes that are reused all over the place.
Now, with user interfaces, it may be acceptable to catch the exception and show the user an error message. So your example where you print the exception's message might be ok, but you'd really need to think about the use-cases of it. Are you calling it from a model or controller? If so, it may be ok display an error message. Are you calling it from a library? If so, it's probably better to let the exception bubble up.
Also, don't use a global try{} catch() {} block. Instead, install an Exception Handler to handle it for you. It's cleaner, and more semantically correct (since any try{}catch{} implies that you know how to handle the exception that you caught, whereas the exception handler is precisely meant for exceptions that weren't handled because you didn't know how to handle them.
Exceptions are for exceptional circumstances. Do not use them for all error conditions. If a user submits a password that's too short, don't throw an exception, handle that in validation. But if your hash function is expecting sha256 to be available and it isn't, that's a time for an exception. Exceptions are useful for program errors (when a condition that is unexpected happens, such as invalid input to a function), state errors (when the application enters a state that is unknown or unstable, such as if the requested view does not exist) and runtime errors (when the application encounters an error that can only be detected at runtime, such as a file-not-found error).
There is an entire page of the PHP manual devoted to extending exceptions and that page also gives you a lot of information on the methods to identify file/line number, backtrace etc. where the exception was thrown. This is the type of information that is extremely useful for debugging.