02 / 2015
Michaël Garrez
A journey from Request to Response
Yes ... but it helps :
A journey from Request to Response
A journey from Request to Response
HTTP protocol implies necessarily a Request and a Response.
How could a Web Framework be based on another principle ?
A journey from Request to Response
A journey from Request to Response
PHP gives us "helpers" to access HTTP Request data : Superglobals ($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER).
PHP methods like echo(), print(), cookies() and headers() gives you an easy way to output a HTTP Response.
A journey from Request to Response
One of Symfony's 31 components.
HttpFoundation provides an object oriented layer for a bunch of HTTP specifications.
A journey from Request to Response
<?php use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); // OR $request = new Request( $_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER );
A journey from Request to Response
HttpFoundation will not sanitize your HTTP Request data, it only provides an object layer to access it
A journey from Request to Response
<?php use Symfony\Component\HttpFoundation\Response; $response = new Response( 'Content', Response::HTTP_OK, array('content-type' => 'text/html') );
A journey from Request to Response
Provides an object layer to start or stop a session and access its data
A journey from Request to Response
A journey from Request to Response
A journey from Request to Response
Symfony uses MVC architectural pattern.
Every single Request will be handle by the Front Controller to be dispatched to the Application
A journey from Request to Response
With the HttpFoundation Component you can actually build a correct Front Controller
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // Le chemin de l'URI demandée if (in_array($path, array('', '/'))) { $response = new Response('Bienvenue sur le site.'); } elseif ($path == '/contact') { $response = new Response('Contactez nous'); } else { $response = new Response('Page non trouvée.', 404); } $response->send();
A journey from Request to Response
Symfony Front Controllers (Depending on your environment)
uses a Kernel to handle things such as routing
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // Le chemin de l'URI demandée if (in_array($path, array('', '/'))) { $response = new Response('Bienvenue sur le site.'); } elseif ($path == '/contact') { $response = new Response('Contactez nous'); } else { $response = new Response('Page non trouvée.', 404); } $response->send();
A journey from Request to Response
Our Front Controller
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // Le chemin de l'URI demandée if (in_array($path, array('', '/'))) { $response = new Response('Bienvenue sur le site.'); } elseif ($path == '/contact') { $response = new Response('Contactez nous'); } else { $response = new Response('Page non trouvée.', 404); } $response->send();
<?php use Symfony\Component\HttpFoundation\Request; require_once __DIR__.'/../app/AppKernel.php'; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
Symfony app.php
NB : boostrap.php.cache call has been removed from app.php for lisibility
A journey from Request to Response
Text
A journey from Request to Response
A journey from Request to Response
<php // Instantiates a new AppKernel in "prod" environment with no debug $kernel = new AppKernel('prod', false); // Will just define you want cache and it's suffix and extension (not a big deal :) !) $kernel->loadClassCache();
A journey from Request to Response
<?php // Trivial HttpFoundation utilisation , isn't it ? $request = Request::createFromGlobals(); // Real business is coming $response = $kernel->handle($request);
A journey from Request to Response
<?php public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { if (false === $this->booted) { $this->boot(); } return $this->getHttpKernel()->handle($request, $type, $catch); }
A journey from Request to Response
A journey from Request to Response
A journey from Request to Response
<?php public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { if (false === $this->booted) { $this->boot(); } return $this->getHttpKernel()->handle($request, $type, $catch); }
A journey from Request to Response
Will convert our Request to a Response
A journey from Request to Response
A journey from Request to Response
<?php private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { $this->requestStack->push($request); // request $event = new GetResponseEvent($this, $request, $type); $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException('Unable to find the controller for path'); } // ...
kernel.request
A journey from Request to Response
The ControllerResolver will basically convert a :
DJUserBundle:Profile:show
To :
DJ\UserBundle\Controller\ProfileController::showAction
Based on the $request->attributes->get('_controller') value
A journey from Request to Response
But how can the ControllerResolver access the attributes property of $request which is empty isn't it ?
A journey from Request to Response
Yes remember :
<?php $request = new Request( $_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER );
A journey from Request to Response
Well, the Routing in Symfony is handled by a listener (RouterListener) on the kernel.request event dispatched before.
The RouterListener is a part of the Routing Component of Symfony
Before the RouterListener other listeners are triggered like SessionListener (part of Session Component), FragmentListener for sub requests
A journey from Request to Response
The purpose of a MatcherInterface is to convert an URI (path exactly) to a route. In the fact the Router implements a RequestMatcherInterface which will call an UrlMatcherInterface child.
In the fact the Router will be rarely called but its cached version appENVUrlMatcher will be called. This file contains whole your application routes and its match() function only checks partial matches through hundred of strpos() calls on the $request->getPathInfo().
A journey from Request to Response
A journey from Request to Response
A journey from Request to Response
<?php public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); // add attributes based on the request (routing) try { $parameters = $this->matcher->matchRequest($request); $request->attributes->add($parameters); unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { throw new NotFoundHttpException(...); } catch (MethodNotAllowedException $e) { throw new MethodNotAllowedHttpException(...); } }
A journey from Request to Response
After the RouterListener, other Listeners will be called for the same event as the Firewall
A journey from Request to Response
A journey from Request to Response
Symfony Firewall will not authenticate users.
Its only goal is to prevent users to access defined restricted areas
A journey from Request to Response
As said before the Firewall will listen to the kernel.request event.
It will try to match a secured area to the current path and will return listeners if match is found.
A journey from Request to Response
<?php use Symfony\Component\Security\Http\FirewallMap; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\Security\Http\Firewall\ExceptionListener; $map = new FirewallMap(); $requestMatcher = new RequestMatcher('^/secured-area/'); // instances de Symfony\Component\Security\Http\Firewall\ListenerInterface $listeners = array(...); $exceptionListener = new ExceptionListener(...); $map->add($requestMatcher, $listeners, $exceptionListener); // the EventDispatcher used by the HttpKernel $dispatcher = ...; $firewall = new Firewall($map, $dispatcher); $dispatcher->addListener(KernelEvents::REQUEST, array($firewall, 'onKernelRequest');
A journey from Request to Response
Those listeners will try to Authenticate the user based on the request and will (depending on the situation) :
A journey from Request to Response
<?php private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { $this->requestStack->push($request); // request $event = new GetResponseEvent($this, $request, $type); $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException('Unable to find the controller for path'); } // ...
A journey from Request to Response
<?php private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // ... // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException('Unable to find the controller for path'); } // ...
The getController() method of the ResolverController will :
A journey from Request to Response
A journey from Request to Response
<?php private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // ... // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(...); } $event = new FilterControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); $controller = $event->getController(); // controller arguments $arguments = $this->resolver->getArguments($request, $controller); // call controller $response = call_user_func_array($controller, $arguments); // Your controller might not be returning a Response right away, this will be explained // in the next slide don't worry :) return $this->filterResponse($response, $request, $type); }
A journey from Request to Response
<?php private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // ... if (!$response instanceof Response) { $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); $this->dispatcher->dispatch(KernelEvents::VIEW, $event); if ($event->hasResponse()) { $response = $event->getResponse(); } if (!$response instanceof Response) { $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); throw new \LogicException($msg); } } return $this->filterResponse($response, $request, $type); }
kernel.view
Symfony implements a kernel.view event to allow developers to implement a view subsystem.
A journey from Request to Response
A journey from Request to Response
Will pop the Request from the RequestStack by calling finishRequest()
Will return the Response to the AppKernel which will return it to the Front Controller
<?php private function filterResponse(Response $response, Request $request, $type) { $event = new FilterResponseEvent($this, $request, $type, $response); $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); $this->finishRequest($request, $type); return $event->getResponse(); }
kernel.reponse
kernel.finish_request
A journey from Request to Response
A journey from Request to Response
<?php $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
A journey from Request to Response
Will add headers()
Will set cookies()
Will simply echo your Response content :
<?php /** * Sends content for the current web response. * * @return Response */ public function sendContent() { echo $this->content; return $this; }
A journey from Request to Response
Will call terminate() from the HttpKernel
Will throw the last Event of our journey !
kernel.terminate
<?php public function terminate(Request $request, Response $response) { $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); }
A journey from Request to Response
A journey from Request to Response
What if any part of my script triggers an exception ?
<?php public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { try { return $this->handleRaw($request, $type); } catch (\Exception $e) { if (false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleException($e, $request, $type); } }
kernel.exception
A journey from Request to Response
Every \Exception thrown will be converted to a FlattenException by the ExceptionListener and passed to the designated Controller to handle them and return a Response
A journey from Request to Response
Symfony allows you to launch sub Requests to render small parts of your website for example.
<?php $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
A journey from Request to Response
A journey from Request to Response
EventDispatcher
DependencyInjection
Finder
OptionsResolver
...
A journey from Request to Response
Symfony provides an HttpCache reverse proxy (which implements HttpKernelInterface like HttpKernel).
It takes an AppKernel as argument.
<?php // ... require_once __DIR__.'/../app/AppCache.php'; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $kernel = new AppCache($kernel); Request::enableHttpMethodParameterOverride(); $request = Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response);
A journey from Request to Response
When you trigger php app/console ... you actually launch an Application.
The Symfony applications also wraps the AppKernel to get access to the bundles, Container, ...
<?php // ... $input = new ArgvInput(); $kernel = new AppKernel($env, $debug); $application = new Application($kernel); $application->run($input);