The HttpKernel component from Symfony is, alone, a powerful component to deal with the Request
to Response
conversion during the Client browser's HTTP Requests. I have been, however, struggling to get things to work as expected.
When I visit the default route I get the Welcome page which looks pretty cool.
I have attempted a few things to get things to work with two of the custom routes that I have defined.
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$routes = $container->get(RouteCollection::class);
$routes->add('hello', new Route('/hello/{name}', [
'_controller' => function (Request $request): Response {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
}]
));
$routes->add('reports', new Route('/reports', [
'_controller' => ['\Demo\Invoice\App\Controller\ReportsController::listAction'],
]));
Furthermore, I have defined my controller as a service as shown below:
// Controllers
$services->set(InvoiceReports::class)
->arg('$twig', service(Environment::class))
->public();
Having attempted to use the ContainerControllerResolver
, apart from the ControllerResolver
that both comes with the said component, I have also tried to write my own Controller Resolver borrowing from the FrameworkBundle.
Both custom routes show blank screens when I visit them on the browser, and I have no idea what is going on with all three Controller resolvers I have tried to work with. Even when I have enabled Debug::enable()
nothing shows up unless it is a fatal error.
Below is how I setup my Kernel implementation so as to take advantage of this powerful component.
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
/**
* @see https://symfony.com/doc/current/components/http_kernel.html#a-full-working-example
*/
return static function (ContainerConfigurator $container): void {
$services = $container->services();
$services->set(RouteCollection::class)
->public();
$services->set(Request::class)
->factory([Request::class, 'createFromGlobals'])
->public();
$services->alias(RequestInterface::class, Request::class)
->public();
$services->set(RequestContext::class)
->public();
$services->set(UrlMatcher::class)
->args([
service(RouteCollection::class),
service(RequestContext::class),
])
->public();
$services->set(RequestStack::class)
->public();
$services->set(RouterListener::class)
->args([
service(UrlMatcher::class),
service(RequestStack::class),
])
->public();
$services->set(EventDispatcherInterface::class, EventDispatcher::class)
->call('addSubscriber', [service(RouterListener::class)])
->public();
$services->set(ControllerResolver::class)
->arg('$logger', service(LoggerInterface::class))
->public();
$services->set(ArgumentResolver::class)
->public();
$services->set(HttpKernel::class)
->args([
service(EventDispatcherInterface::class),
service(ControllerResolver::class),
service(RequestStack::class),
service(ArgumentResolver::class),
])
->public();
$services->set(Response::class)
->public();
};
As mentioned before the above configuration seems to work, except it is so frustrating to not know what I am doing wrong when it comes to defining my class-based controllers.
How can I get my agnostic framework to resolve my controller classes?
I have my codebase hosted on github should anyone be interested to poke around it.
After reading the link provided by @yivi I was able to get things to work. Below are the steps I have taken after reconfiguring my stack.
The route definitions did not change much as they were in the correct order.
$routes->add('reports', new Routing\Route('/', [
'_controller' => [CoolStuff\App\Controller\ReportsController::class, 'showAction'],
]));
The Symfony Routing Component is independent of the Symfony Dependency Injection component. Through Symfony HttpKernel Component, we can link the two together to achieve what I wanted through the provided controller resolvers.
It is also crucial to make sure that the Controller class can be resolved through the container by registering it as a service.
$services->set(ReportsController::class)
->arg('$authService', service(AuthService::class))
->arg('$invoiceService', service(InvoiceService::class))
->arg('$twig', service(Environment::class))
->arg('$log', service(LoggerInterface::class))
->tag('controller.service_arguments')
->public();
Since we have turned auto-wiring support off we also have to mark the service as public so that we can be able to use the get
method when resolving it out of the container. Furthermore, our service has to be tagged with controller.service_arguments
. The arguments are pretty standard, but they are the crux of the issue I was having because I was using an incorrect Controller Resolver amongst other things.
I have changed the binding of the ControllerResolverInterface
; instead of using ControllerResolver
I settled for the ContainerControllerResolver
so that we can rely on the Container to resolve our Controller class definitions. This way we can be able to inject the dependencies we need through our Controller's construct method.
$container->register('controller_resolver', ContainerControllerResolver::class)
->setArguments([$container, new Reference(LoggerInterface::class)]);
With the three points above I was able to solve my problem and had everything working perfectly from the MVC point of view.