I am working on an Symfony + FOSRestBundle based REST API. I have implemented (extended) custom HttpException class which is being used in case of validation errors. Along the error message I always pass an array with detailed data about validation problems which is generated by validator class. That is basically the reason why I extended base HttpException. My idea is to show array of errors from exception to the end user of API, but with the core functionality of Symfony I always get "400 Bad request" error in JSON format (no possibility to show another key/value pair along status code and message). I started looking how could I overcome this problem and tried various principles (normalizers, listeners, ...) which I found after searching the web, but none of those really solved the problem of showing the array of errors along code and message.
I'm using Twig for generating responses (I love detailed HTML errors in dev mode, so there's no way for me to replace it with something else). I checked how HTTP kernel processes the request when an exception is thrown and found out that everything goes via exception listener to the exception controller. I created custom exception controller and tried to get the array of errors from the exception. However, the real exception is wrapped into FlattenException which discards that array. The main problem here is actually FlattenException object, because I can't get the data from it about the errors (I know why flatten exception is used there, but in my case is not really useful since I have the real array, not some active object instead of it).
I would like to somehow replace FlattenException with my custom class - I want extend it just to have another property for array of messages that would be preserved until exception controller's showAction and rendering of view template. Then I would change static create function with additional logic for handling cases when my custom type of exceptions is being wrapped. I'm not sure if that's even possible without changing the core code of Symfony and if possible I'd like to stay away from any hacks.
Another thing about which I was thinking is to extend/replace ExceptionListener. There I would just change existing logic to replace FlattenException with MyCustomFlattenException which would then get passed to my ExceptionController via showAction method. I already tried that and overrode both twig.exception_listener and http_exception_listener services (separately - once the first and in the other case the second; I did that in services.yaml), but didn't have much luck with it.
For the twig.exception_listener this is what is in my service ...
twig.exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
$controller: "%twig.exception_listener.controller%"
$logger: "logger"
tags:
- name: kernel.event_subscriber
- name: monolog.logger
channel: request
... and I always get the this error:
Fatal error: Uncaught Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service "App\EventListener\ApiExceptionListener": argument "$controller" of method "Symfony\Component\HttpKernel\EventListener\ExceptionListener::__construct()" has no type-hint, you should configure its value explicitly. in /project/vendor/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php on line 54
While for http_exception_listener this is what's in my service ...
http_exception_listener:
class: App\EventListener\ApiExceptionListener
arguments:
[
null,
null,
"%kernel.debug%",
"%kernel.charset%",
"%debug.file_link_format%",
]
tags:
- name: kernel.event_listener
event: kernel.exception
method: onKernelException
priority: -1024
- name: kernel.reset
method: reset
... and there is no any error, but still the core ExceptionListener is called instead of the one I specified in services.
This is the ApiExceptionListener class where I have some breakpoints in my IDE to check if it was really called:
<?php
namespace App\EventListener;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ApiExceptionListener extends ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$test = 'ok';
}
protected function duplicateRequest(\Exception $exception, Request $request)
{
parent::duplicateRequest($exception, $request);
}
}
I would really appreciate any help or idea to get in the right direction of solution for this problem. Thank you!
Related
I need to throw an Exception inside service
$isLangExist = $this->em->getRepository('TranslationBundle:Language')->findOneBy(array(
'locale' => $this->request->getMasterRequest()->getLocale()
));
if (!$isLangExist) {
throw new createNotFoundException('you are using unavailable langage');
}
but I got this page is not workinc in prod env
how can i show 404 page with createNotFoundException or any another Exception type
thanks
It looks like your code is not correct. I would expect it to look like this:
if (!$isLangExist) {
throw $this->createNotFoundException('you are using unavailable langage');
}
The method is part of the abstract base controller you can use, but it's not mandatory. What should work in cases where you don't extend this controller is:
if (!$isLangExist) {
throw new NotFoundHttpException('you are using unavailable langage');
}
Your problem goes beyond the above code, because you don't throw the exception inside a controller as I expected. You throw it inside a Twig extension. This exception will interrupt rendering, which is why the error is not converted into a 404 exception and instead is treated as a 500 error. Potentially you will see other 500-errors with your extension whenever one of the queries fails, which is probably not what you want. Addressing this issue likely requires rethinking how you use these global twig variables.
You could try moving the templates that use these variables into separate templates being rendered by a dedicated controller using sub requests or ESI:
{{
render(controller(
'AppBundle:Global:_listCategories',
{
'locale': app.request.attributes.get('_locale')
}
))
}}
Another solution might be to set these with null or an error-object whenever something fails and then react to these "alternative" results in your template, which is not what I would prefer.
There are probably many other ways to tackle this. The gist is: rendering error are different than http-exceptions thrown by controllers services. You have to ensure that your templates can either be rendered despite these missing/faulty variables or deal with these missing parameters before rendering the templates, e.g. in an event listener.
In order to get the flash bag of the session and add a flash message, in the controller I call:
$request->getSession()->getFlashBag()->addFlash(...);
(where $request is an instance of Request)
but I get the following IDE type error:
Method 'getFlashBag' not found in
null|\Symfony\Component\HttpFoundation\Session\SessionInterface
The problem is that $request->getSession() returns a SessionInterface, which does not contain the getFlashBag method.
That's why the IDE is complaining, even if the actual object returned by that method is an instance of the Session class which has the getFlashBag method.
When inside a controller, a quick solution can just be using:
$this->addFlash(...);
instead of:
$request->getSession()->getFlashBag()->addFlash(...);
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 am using the Stripe API and Laravel together. If Stripe detects an error charging the card (such as using a test credit card number that throws an invalid security code error), the API bindings are supposed to throw an exception, which they do. The problem is, I am having issues catching the exception before Laravel throws up the error 500 page (I am trying to perform a redirect with an error message instead).
The code I've written is available on Pastebin: http://pastebin.com/ZaW2xbbt
The behavior I'm expecting is for the catch to fire and the redirect to be performed, but instead, I get the stack trace with the message and "Unhandled Exception". That's confusing me because I am handling the exception.
Variables such as $customer are valid and have been defined previously. Any ideas what's going on?
For any future viewers, here's an article on error handling in laravel 4.
Laravel 4 lets you catch Exceptions by exception type. For instance, you can handle Symfony's HttpException and of its sub-classes by adding this to your code:
// Catch HttpException, NotFoundHttpException, etc etc
App::error(function(HttpException $exception, $code, $fromConsole)
{
...
});
Symfony HttpExceptions (used in Laravel) can be found here.
You can also throw this in a ServiceProvider:
<?php namespace My\Namespace;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\HttpKernel\Exception\HttpException;
class MyServiceProvider extends ServiceProvider {
public function register()
{
$this->app->error(function(HttpException $exception, $code, $fromConsole)
{
...
});
}
}
Hope that helps!
Generally, all errors logged by Laravel are logged under storage/logs folder
Anyway, the 500 error could be a syntax/parse error, in such case the Laravel framework could be not yet loaded when the error occurs and if so, the exception is not lhandled by Laravel.
In this case you should access the apache/vargrant/whatif php error log in some way (dependently on your server capabilities and configuration), in my personal cases I have configured the server to put that logs in a /storage/logs/error_log.txt file such that I can access them as other Laravel server logs
Note that in Laravel 5, you have app/Exceptions/Handler.php as entry point for customize exception handling/reporting
https://laravel.com/docs/5.7/errors#the-exception-handler
I have been reading the following question here: CakePHP 2.0 - How to make custom error pages?
About creating custom views for exception handling in CakePHP 2.0+ and have been using it as a base to start doing the same in my own application hence starting my own question.
However I'm not following the logic. For example how does the Throw NotFoundException know to call the notFound method in the Errors Controller as I don't see any direct relationship in terms of the naming... Unless I'm missing the point?
In any case I'm looking to add 404, 403, and 401 errors and then be able to create custom views and call them using the exception handler throughout my app.
Can anyone shed more light on this? I'm using the latest version of Cake 2.1
So I have the following code:
App::uses('ExceptionRenderer', 'Error');
class AppExceptionRenderer extends ExceptionRenderer {
public function notFound($error) {
$this->controller->redirect(array('controller' => 'errors', 'action' => 'error404'));
}
}
And I want to replace that redirect with rendering a custom error view:
I've tried:
$this->controller->layout = null;
$this->controller->render('/Errors/error404');
But that just shows a blank page... Can anyone help me out as I don't want to do the redirect and would much rather follow conventions and render actual views with the same url when getting errors.
Update: Also noticed that the custom views ONLY get called when exceptions are manually called in the controller, and not for actual errors such as domain.com/garbageurl or something else... So it doesn't seem to be doing what I thought!
Have a look at these files from core Cake:
Cake/Error/ErrorHandler.php
Cake/Error/ExceptionRenderer.php
Here's what's happening:
ErrorHandler::handleException() is your exception handler. It gets called when an exception is thrown.
ErrorHandler::handleException() calls ExceptionRenderer::__construct() (your custom exception renderer must extend ExceptionRenderer) which parses the name of the Exception that was thrown, and from that, sets $this->method.
ErrorHandler::handleException() then calls ExceptionRenderer::render() which uses call_user_func_array() to call the method whose name is $this->method.
I was just looking for the same thing and could not find a neat way to do this using AppExceptionRenderer. It just won't allow you to have separate error403 and error404 template files.
So I just did this in my /app/View/Errors/error400.ctp file instead...
<? if ($error instanceof ForbiddenException) { ?>
<h4>Whoops! The page you attempted to access
requires permissions that you don't have.</h4>
<? } else { ?>
<h4>Whoops! We couldn't find the page you were looking for, sorry!</h4>
<? } ?>