I have an application in which my controllers are broken up into specific groups (API, CMS and front-end), this is already set up using Router groups. My question is how would one go about creating custom error handling for each group.
For example, when an exception occurs in any API controller action I would like to send back json with an error code and message, an exception in the CMS would output a detailed error page, and an exception on the front end would send the user to a generic 404 or 500 error page (as appropriate).
I am aware of how I could do this manually in each controller action, but that might get very repetitive. Ideally, I would want to create one handler for each and automatically use it across the board.
I am also aware of the App\Exceptions\Handler class, but if any of the controller groups could return a ModelNotFoundException, for example, how do I differentiate where the exception came from?
Is there another place that this type of exception handler could be inserted?
If you go to app\Exceptions\Handler.php file (which you mentioned) you can do it what you want.
You could for example define your render function this way:
public function render($request, Exception $e)
{
$trace = $e->getTraceAsString();
if ($e instanceof ModelNotFoundException
&& mb_strpos($trace, 'app\Http\Controllers\WelcomeController')
) {
return response()->json('Exception ' . $e->getMessage());
} elseif ($e instanceof ModelNotFoundException) {
return "General model not found";
}
return parent::render($request, $e);
}
and add to imports use Illuminate\Database\Eloquent\ModelNotFoundException;
and now if you throw ModelNotFoundException in your WelcomeController or you for example run another class' method from your WelcomeController and this method will throw ModelNotFoundException you can track it and return json response. In other cases if exception will be instance of ModelNotFoundException you can render your exception in other way and in all other cases you can use standard exception method to render.
This way you don't need to define anything in your controller, you can do it just in Handler and it it's more complicated you could create separate methods or create new classes to handle specific exceptions in the way you want and run them in this handler.
Related
From my understanding there can only be one exception handler which will catch all unhandled exceptions. However, is there an elegant way to have a specific exception handler for Controllers A and B, and controllers C, D, etc. would be handled by default exception handler?
I don't think this is possible by default, other than creating a kernel eventlistener. However, you can create something simple yourself.
In a Controller Method, you might could do something like
public function index()
{
try {
// Do normal logic here.
} catch (\Exception $e) {
// Do whatever you want with any exception.
// Or Call your exception handler: MyExceptionHandler::handle()
}
}
You can also create a custom AbstractController so it might be a bit simpler. In which you extend the render function, so you abstract that logic away
How can I catch any exceptions to cover the 100% code coverage report? This only covers the try condition in the code.
Controller
public function getItem()
{
try {
// Some code
return $result;
} catch (Exception $e) {
Log::error($e->getMessage());
throw new Exception ("$e->getMessage ()", 500);
}
}
Test file
public function testGetItem()
{
$this->get('api/getitem')->assertStatus(200);
}
Testing exceptions is easy in PHPUnit, but doesn't work like you'd expect in Laravel thanks to how it handles exceptions.
To test exceptions in Laravel you first need to disable Laravel exception handling - which if you extend the provided TestCase, you can do with the withoutExceptionHandling() method.
From there you can use PHPUnit's expectException() methods. Here's a small example.
use Tests\TestCase;
class ExceptionTest extends TestCase
{
public function testExceptionIsThrownOnFailedRequest()
{
// Disable exception handling for the test.
$this->withoutExceptionHandling();
// Expect a specific exception class.
$this->expectException(\Exception::class);
// Expect a specific exception message.
$this->expectExceptionMessage('Simulate a throw.');
// Expect a specific exception code.
$this->expectExceptionCode(0);
// Code that triggers the exception.
$this->get('/stackoverflow');
}
}
Now when the test is run, it'll disable Laravel's exception handling for this test run, then we set some expectations about what should happen, lastly, we call the code that'll fulfill those expectations, in this case, that's a get() call to route.
Now how the expectations are fulfilled will be up to your application.
In PHP,is there any way to handle exceptions globally using one method? If there is, how could I do that?
I want to use the method which handles exception across all classes.
The reason that I want to achieve this is I do not want to repeat same code all the time.
Here is the example of the exception handling.
try {
***SOME CODE TO EXCUTE***
}
catch ( Exception $e ) {
/* custom function to handle exception */
$msg = exceptionHandler($e);
$this->error(0,500, $e->getMessage());
}
Working with Symfony 2, I created an exception event listener to catch my exceptions. It works good and inside of it I get the code of the exception like this :
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$code = $exception->getCode()
....
}
For example, the exception I catch is a problem in a twig template. a variable does not exist.
The code of the exception is 0.
What does that mean ?
Where can I find a list of the different codes ?
Thank you
The exception code 0 is the default code in PHP. You can see it in the \Exception constructor.
Main of Symfony Exception have their code to zero because it's not overriden. And it's very difficult to get a complete list of all excetions' codes.
However, concerning your custom exceptions, that you created in your application, you can set their codes with values you want and handle them in your Event Listener.
For example, you could have something like :
class ResourceNotFoundException extends \Exception
{
public function __construct($message = '')
{
return parent::__construct($message, 404);
}
}
As a result, your exception's code for this exception will be 404. Now your application design and the traitment you want to do in your event listener will guide you in you devlepment.
As well as many other, I'm trying to create my own MVC to improve my knowledge etc. :)
I'd like to ask about handling errors/exceptions in MVC pattern.
Dispatcher is getting information from Router about Route and getting Controller's and method's name from Route. Then dispatcher is trying to create instance of this Controller and execute this method.
Now let's imagine that our method is throwing exception (yes, it is not catched in method because of bad code :)). It could be custom made exception like NotFoundException or ApplicationInternalErrorException or one of SPL excpetions like RuntimeErrorException or InvalidArgumentException. It doesn't matter, really.
Of course, we want Dispatcher catch this exception, log it and respond accordingly exception type: display 404 page if NotFoundException was catched etc.
What is the best practice for doing it? Is Dispatcher a right place for doing it? Maybe the best place is FrontController (but I'm not using this pattern; Dispatcher is creating Controller instance.
Simple code:
class Dispatcher {
public function dispatch() {
$controller = $this->getRouter()->getController();
$method = $this->getRouter()->getMethod();
$args = $this->getRouter()->getArguments();
try {
call_user_func_array(array($controller, $method), array_values($args));
} catch (Exception $e) {
Logger::log($e->__toString);
if ($e instanceof NotFoundPage) {
call_user_func(array('ErrorController', 'notFound'))
} elseif ($e instanceof ...) {
...
} elseif ($e instanceof ...) {
...
} else {
...
}
}
}
}
Is it ok, or it is not a very good practice?
Thank you.
I'm simply using an external file (xml, yaml or anything you like) which contains all the routes for my web application. When a request comes the Router tries to match the request url to one of the routes I have in my file. If there were no match it simply redirects to the 404 page with a 404 header. I think that you don't need try/catch blocks for this.
I took the idea from Symfony.