Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[DX] Better exception handling (as opposed to the current twig controller) #11752

Closed
@NinoFloris

Description

@NinoFloris

Digest (TL;DR)

A small service that allows tagged controllers to handle the exception(s) they registered for, optionally bubbling via inheritance to the closest match in the inheritance chain.
To replace, but incorporate, the current inflexible twig controller.
For my reasoning, explanations, and implementation details read the long story.

Code can be found here: https://gist.github.com/NinoFloris/92523a34cdf17a084d4d

Would I do good to integrate this into the framework and issue a pull request?

Long story

I have written a small class because I was fed up with the current state of afairs around event based exception handling (kernel event exception).
I like to fire my exceptions from all over the place and let them be handled by an exception listener.
This eases my boilerplate code by a huge amount, e.g. not having to check for the request format (which response would the request like to receive, html, json, xml). Talking about that, see #10538.
I then don't have to repeat myself over and over again with this identical logic. The exception listener/controller will do this all for me.

The problem

My problem with the way it is configured currently is that it isn't flexible. When I want to create some specific behavior for my app exceptions (all, say, inheriting from MyAppException) With the current setup I have 3 options.

  1. Override the view templates of the twig exception controller
  2. Override the twig exception controller method (config.yml)
  3. Roll my own exception listener and fire a subrequest to my own exception controller.

The downsides of all these options are:

  1. I cannot specify an exception design template per bundle. (e.g. error in the FrontendBundle should be able to look different than in the AdminBundle) And I cannot differentiate based on exception class but only on the given exception code.
  2. When I override the twig exception controller I don't have a lot of flexibility, the findtemplate function still handles the search the same so I now need to roll my own templatefinder. I also need to duplicate the functionality the original controller had because otherwise I would be degrading the UX for certain exceptions (e.g. checking for http statuscodes comes to mind).
  3. Currently the best option but also immediately the one that requires the most configuration and time.

These options are all not ideal and especially option 3 is very time consuming, but currently the only way to go.

Solution

I created an exception listener that almost exactly mirrors the one currently used in the httpkernel HttpKernel\Exception\Exceptionlistener. It even uses the same signature to call the controller method.

On top of this I created a class (naming is open for discussion) ExceptionControllers
This class is a tag handler class for the tag myapp_service.exception_controller
This tag has 1 mandatory and 3 optional attributes

  1. exception = the exception you want to handle with this exception controller
  2. method = the controller methodname that gets called (defaults to "showAction")
  3. match = exact or inherited, does the controller only match exceptions with the exact classname or does it allow children of its handled exception. (defaults to "inherited")
  4. bundle-scope = the site/origin of the thrown exception you want to handle e.g. only handle RuntimeException if it originated from the MyAppFrontBundle (defaults to "" which is global)

This ExceptionControllers class is constructor injected into the exception listener which can then 'query' the class for a correct handler.
The priority for handling is narrow, bundle, scope first. Closest match in the inheritance chain is preferred. When a candidate controller set match="exact" the inheritance is n=1 (just itself).
Finally it is up to the controller to issue the correct response and do (additional) logging.

About BC

This solution keeps BC because we could still allow the twig controller to be overridden via the config.yml. What needs to change is the internal handling for the HttpKernel\Exception\Exceptionlistener class which would then get the ExceptionControllers class injected, replacing the "controller" string.

The twig exception controller would then have to be tagged like so (tag name would then be changed of course)
<tag name="myapp_service.exception_controller" exception="Exception" />
Where match="inherited", method="showAction" and bundle-scope="" are implied through defaults.
This will bubble all the unhandled exceptions to this controller if there is no controller closer to the exception in the inheritance chain or a controller in a narrower scope. That is all :D

Examples

<tag name="myapp_service.exception_controller" exception="MyApp\ServiceBundle\Exception\MyAppException" match="exact" />

<tag name="myapp_service.exception_controller" exception="MyApp\ServiceBundle\Exception\MyAppException" match="inheritance" bundle-scope="MyAppFrontBundle" />

Future ideas

Let the ExceptionListener fall back to the next best controller to handle the exception. If the previous controller did not produce a response or threw an error. However we should limit this to trying a maximum amount of controllers to prevent the final response from possibly taking waaay too long.

BC breaking changes

  1. For the best experience the ExceptionListener should refrain from logging and let the controllers handle that. I use an abstract class BaseExceptionController with the logException method from the current ExceptionListener in it to very easily do the logging the ExceptionListener did.
    Possible fix is to only log if the controller is in some way related to the twig controller approach and is therefore using "the old way" (need to be smart about overridden twig controllers).
  2. The dependency ExceptionControllers has on Kernel.
    It could easily be made optional through property injection, or attribute on-invalid="null" and shutting off the bundle-scope functionality if the Kernel is not defined.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DXDX = Developer eXperience (anything that improves the experience of using Symfony)TwigBundle

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions