<h2 dir="auto">Digest (TL;DR)</h2>
<p dir="auto">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.<br>
To replace, but incorporate, the current inflexible twig controller.<br>
For my reasoning, explanations, and implementation details read the long story.</p>
<p dir="auto">Code can be found here: <a href="https://gist.github.com/NinoFloris/92523a34cdf17a084d4d">https://gist.github.com/NinoFloris/92523a34cdf17a084d4d</a></p>
<p dir="auto">Would I do good to integrate this into the framework and issue a pull request?</p>
<h2 dir="auto">Long story</h2>
<p dir="auto">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).<br>
I like to fire my exceptions from all over the place and let them be handled by an exception listener.<br>
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 <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="30226187" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/10538" data-hovercard-type="issue" data-hovercard-url="/symfony/symfony/issues/10538/hovercard" href="https://github.com/symfony/symfony/issues/10538">#10538</a>.<br>
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.</p>
<h4 dir="auto">The problem</h4>
<p dir="auto">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 <code class="notranslate">MyAppException</code>) With the current setup I have 3 options.</p>
<ol dir="auto">
<li>Override the view templates of the twig exception controller</li>
<li>Override the twig exception controller method (<code class="notranslate">config.yml</code>)</li>
<li>Roll my own exception listener and fire a subrequest to my own exception controller.</li>
</ol>
<p dir="auto">The downsides of all these options are:</p>
<ol dir="auto">
<li>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.</li>
<li>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).</li>
<li>Currently the best option but also immediately the one that requires the most configuration and time.</li>
</ol>
<p dir="auto">These options are all not ideal and especially option 3 is very time consuming, but currently the only way to go.</p>
<h4 dir="auto">Solution</h4>
<p dir="auto">I created an exception listener that almost exactly mirrors the one currently used in the httpkernel <code class="notranslate">HttpKernel\Exception\Exceptionlistener</code>. It even uses the same signature to call the controller method.</p>
<p dir="auto">On top of this I created a class (naming is open for discussion) <code class="notranslate">ExceptionControllers</code><br>
This class is a tag handler class for the tag <code class="notranslate">myapp_service.exception_controller</code><br>
This tag has 1 mandatory and 3 optional attributes</p>
<ol dir="auto">
<li><code class="notranslate">exception</code> = the exception you want to handle with this exception controller</li>
<li><code class="notranslate">method</code> = the controller methodname that gets called (defaults to "showAction")</li>
<li><code class="notranslate">match</code> = 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")</li>
<li><code class="notranslate">bundle-scope</code> = the site/origin of the thrown exception you want to handle e.g. only handle <code class="notranslate">RuntimeException</code> if it originated from the MyAppFrontBundle (defaults to "" which is global)</li>
</ol>
<p dir="auto">This <code class="notranslate">ExceptionControllers</code> class is constructor injected into the exception listener which can then 'query' the class for a correct handler.<br>
The priority for handling is narrow, bundle, scope first. Closest match in the inheritance chain is preferred. When a candidate controller set <code class="notranslate">match="exact"</code> the inheritance is <code class="notranslate">n=1</code> (just itself).<br>
Finally it is up to the controller to issue the correct response and do (additional) logging.</p>
<h4 dir="auto">About BC</h4>
<p dir="auto">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 <code class="notranslate">HttpKernel\Exception\Exceptionlistener</code> class which would then get the <code class="notranslate">ExceptionControllers</code> class injected, replacing the "controller" string.</p>
<p dir="auto">The twig exception controller would then have to be tagged like so (tag name would then be changed of course)<br>
<code class="notranslate">&lt;tag name="myapp_service.exception_controller" exception="Exception" /&gt;</code><br>
Where <code class="notranslate">match="inherited"</code>, <code class="notranslate">method="showAction"</code> and <code class="notranslate">bundle-scope=""</code> are implied through defaults.<br>
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</p>
<h4 dir="auto">Examples</h4>
<p dir="auto"><code class="notranslate">&lt;tag name="myapp_service.exception_controller" exception="MyApp\ServiceBundle\Exception\MyAppException" match="exact" /&gt;</code></p>
<p dir="auto"><code class="notranslate">&lt;tag name="myapp_service.exception_controller" exception="MyApp\ServiceBundle\Exception\MyAppException" match="inheritance" bundle-scope="MyAppFrontBundle" /&gt;</code></p>
<h4 dir="auto">Future ideas</h4>
<p dir="auto">Let the <code class="notranslate">ExceptionListener</code> 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.</p>
<h4 dir="auto">BC breaking changes</h4>
<ol dir="auto">
<li>For the best experience the <code class="notranslate">ExceptionListener</code> should refrain from logging and let the controllers handle that. I use an abstract class <code class="notranslate">BaseExceptionController</code> with the <code class="notranslate">logException</code> method from the current <code class="notranslate">ExceptionListener</code> in it to very easily do the logging the <code class="notranslate">ExceptionListener</code> did.<br>
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).</li>
<li>The dependency <code class="notranslate">ExceptionControllers</code> has on <code class="notranslate">Kernel</code>.<br>
It could easily be made optional through property injection, or attribute <code class="notranslate">on-invalid="null"</code> and shutting off the <code class="notranslate">bundle-scope</code> functionality if the Kernel is not defined.</li>
</ol>
<p dir="auto"><strong>Symfony version(s) affected</strong>: 3.4.23</p>
<p dir="auto"><strong>Description</strong><br>
If you work with various translation xliff-files and one of these has a duplicate key, the error message doesn't say what file is affected:<br>
<code class="notranslate">Invalid resource provided: "1.2"; Errors: [ERROR 1877] Element '{urn:oasis:names:tc:xliff:document:1.2}trans-unit': Duplicate key-sequence ['76'] in key identity-constraint '{urn:oasis:names:tc:xliff:document:1.2}K_unit_id'. (in /var/www/tickeos/web/ - line 314, column 0)</code></p>
<p dir="auto"><strong>How to reproduce</strong></p>
<ul dir="auto">
<li>use <code class="notranslate">Symfony\Component\Translation\TranslatorInterface</code> <code class="notranslate">trans()</code></li>
<li>include a xliff-file with duplicate keys e.g.<br>
<code class="notranslate">&lt;trans-unit id="76"&gt;...&lt;/trans-unit&gt;&lt;trans-unit id="76"&gt;...&lt;/trans-unit&gt;</code></li>
</ul>
<p dir="auto"><strong>Possible Solution</strong></p>
<ul dir="auto">
<li>error says what file is affected</li>
</ul>