title: Symfony - Changing how controllers are instantiated and executed
body: <blockquote>
    <p><em>Note:</em> as of version 2.8, Symfony provided <code>autowire: true</code> for service configuration, and as of version 3.3, Symfony provided <code>alias</code> (instead of <code>autowire_types</code>) to alias a concrete object to an interface for automatic dependency injection into 'controllers as services'. There's also a bundle to allow autowiring for controller 'action' methods, although I've moved away from this and have focussed more on a variation of the ADR pattern (which is, basically, a single 'action' class with an interface method and not shoving a load of actions methods within a single class which eventually makes for an architectural nightmare). This is, effectively, what I've been looking for all these years and now no longer need to 'hook-in' a decent recursive dependency injector (auryn) as the framework now handles what it should have four years previous. I'll leave this answer here in case anyone wants to trace the steps that I did to see how the kernel works and some of the options available at this level.</p>
  </blockquote>
  
  <hr>
  
  <blockquote>
    <p>Note: Although this question primarily targets Symfony 3, it should also be relevant to users of Symfony 2 as the kernel logic doesn't seem to have changed much.</p>
  </blockquote>
  
  <p>I want to change how controllers are instantiated in Symfony. The logic for their instantiation currently resides in <a href="https://github.com/symfony/http-kernel/blob/master/HttpKernel.php#L57" rel="noreferrer">HttpKernel::handle</a> and, more specifically, <a href="https://github.com/symfony/http-kernel/blob/master/HttpKernel.php#L139" rel="noreferrer">HttpKernel::handleRaw</a>. I want to replace <code>call_user_func_array($controller, $arguments)</code> with my own injector performing that specific line instead.</p>
  
  <p>The options I have tried thus far:</p>
  
  <ul>
  <li>Extending <code>HttpKernel::handle</code> with my own method and then having this called by symfony</li>
  </ul>
  
  <pre><code>http_kernel:
      class: AppBundle\HttpKernel
      arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
  </code></pre>
  
  <p>The downside of this is that, because <code>handleRaw</code> is private, I can't extend it without hacky reflection and so I would have to copy and paste a tonne of code.</p>
  
  <ul>
  <li>Creating and registering a new controller resolver</li>
  </ul>
  
  <pre><code>controller_resolver:
      class: AppBundle\ControllerResolver
      arguments: []
  </code></pre>
  
  <p>This was a fundamental misunderstanding I had so I thought I'd document it here. The resolver's job is to resolve <em>where</em> to find the controller as a callable. It hasn't actually been called yet. I am more than happy with how Symfony takes the routes from <code>routes.yml</code> and figures out the class and method to call for the controller as a callable.</p>
  
  <ul>
  <li>Adding an event listener on <code>kernel.request</code></li>
  </ul>
  
  <pre><code>kernel.request:
      class: MyCustomRequestListener
      tags:
          - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
  </code></pre>
  
  <p>Taking a look at the <a href="http://symfony.com/doc/current/components/http_kernel/introduction.html#the-kernel-request-event" rel="noreferrer">Http Kernel Component Documentation</a>, we can see that it has the following typical purpose:</p>
  
  <blockquote>
    <p>To add more information to the Request, initialise parts of the system, or return a Response if possible (e.g. a security layer that denies access).</p>
  </blockquote>
  
  <p>I figured that by creating a new listener, using my custom injector to create my controller, and then return a response in that listener, would bypass the rest of the code that instantiates the controller. <strong>This is what I want</strong>! But there's a major flaw with this:</p>
  
  <p><em>The Symfony Profiler doesn't show up or any of that stuff, it's just my response and that's it. Dead.</em> I found that I can switch the priority from 31 to 33 and have it switch between my code and Symfonys, and I believe this is because of the router listener <a href="http://symfony.com/doc/current/reference/events.html#kernel-request" rel="noreferrer">priority</a>. I feel I'm going down the wrong path here.</p>
  
  <ul>
  <li>Listening on the <a href="http://symfony.com/doc/current/components/http_kernel/introduction.html#the-kernel-controller-event" rel="noreferrer">kernel.controller</a> event.</li>
  </ul>
  
  <p>No, this allows me to change the <strong>callable</strong> that will be called by <code>call_user_func_array()</code>, not how the controller is actually instantiated, which is my goal.</p>
  
  <p>I've documented my ideas but I'm out. How can I achieve the following?</p>
  
  <ul>
  <li>Change how the controllers are instantiated and then executed, specifically <code>call_user_func_array()</code> which is in a bloody private method (thanks Symfony)</li>
  <li>Fall back to the default controller instantiation if mine doesn't work</li>
  <li>Allow everything else to work as expected, like the profiler loading</li>
  <li>Be able to bundle this up with an extension for other users</li>
  </ul>
  
  <hr>
  
  <p><strong>Why</strong> do I want to do this? </p>
  
  <p>Controllers can have many different methods for different circumstances and each method should be able to typehint for what it individually requires rather than having a constructor take all the things, some of which may not even be used depending on the controller method being executed. Controllers don't <em>really</em> adhere to the Single Responsibility Principle, and they're an 'object edge case'. But they are what they are.</p>
  
  <p>I want to replace how controllers are created with my own recursively autowiring injector, and also how they are executed, again with recursive introspection via my injector, as the default Symfony package does not seem to have this functionality. Even with the latest "autowire" service option in Symfony 2.8+.</p>