HttpKernel.php 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. /*
  3. * This file is part of the Symfony framework.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpKernel\HttpKernelInterface;
  14. use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
  15. use Symfony\Component\DependencyInjection\ContainerInterface;
  16. use Symfony\Component\HttpKernel\HttpKernel as BaseHttpKernel;
  17. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  18. /**
  19. * This HttpKernel is used to manage scope changes of the DI container.
  20. *
  21. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  22. */
  23. class HttpKernel extends BaseHttpKernel
  24. {
  25. private $container;
  26. private $esiSupport;
  27. public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver)
  28. {
  29. parent::__construct($dispatcher, $controllerResolver);
  30. $this->container = $container;
  31. }
  32. public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
  33. {
  34. $request->headers->set('X-Php-Ob-Level', ob_get_level());
  35. $this->container->enterScope('request');
  36. $this->container->set('request', $request, 'request');
  37. try {
  38. $response = parent::handle($request, $type, $catch);
  39. } catch (\Exception $e) {
  40. $this->container->leaveScope('request');
  41. throw $e;
  42. }
  43. $this->container->leaveScope('request');
  44. return $response;
  45. }
  46. /**
  47. * Forwards the request to another controller.
  48. *
  49. * @param string $controller The controller name (a string like BlogBundle:Post:index)
  50. * @param array $attributes An array of request attributes
  51. * @param array $query An array of request query parameters
  52. *
  53. * @return Response A Response instance
  54. */
  55. public function forward($controller, array $attributes = array(), array $query = array())
  56. {
  57. $attributes['_controller'] = $controller;
  58. $subRequest = $this->container->get('request')->duplicate($query, null, $attributes);
  59. return $this->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
  60. }
  61. /**
  62. * Renders a Controller and returns the Response content.
  63. *
  64. * Note that this method generates an esi:include tag only when both the standalone
  65. * option is set to true and the request has ESI capability (@see Symfony\Component\HttpKernel\HttpCache\ESI).
  66. *
  67. * Available options:
  68. *
  69. * * attributes: An array of request attributes (only when the first argument is a controller)
  70. * * query: An array of request query parameters (only when the first argument is a controller)
  71. * * ignore_errors: true to return an empty string in case of an error
  72. * * alt: an alternative controller to execute in case of an error (can be a controller, a URI, or an array with the controller, the attributes, and the query arguments)
  73. * * standalone: whether to generate an esi:include tag or not when ESI is supported
  74. * * comment: a comment to add when returning an esi:include tag
  75. *
  76. * @param string $controller A controller name to execute (a string like BlogBundle:Post:index), or a relative URI
  77. * @param array $options An array of options
  78. *
  79. * @return string The Response content
  80. */
  81. public function render($controller, array $options = array())
  82. {
  83. $options = array_merge(array(
  84. 'attributes' => array(),
  85. 'query' => array(),
  86. 'ignore_errors' => !$this->container->getParameter('kernel.debug'),
  87. 'alt' => array(),
  88. 'standalone' => false,
  89. 'comment' => '',
  90. ), $options);
  91. if (!is_array($options['alt'])) {
  92. $options['alt'] = array($options['alt']);
  93. }
  94. if (null === $this->esiSupport) {
  95. $this->esiSupport = $this->container->has('esi') && $this->container->get('esi')->hasSurrogateEsiCapability($this->container->get('request'));
  96. }
  97. if ($this->esiSupport && $options['standalone']) {
  98. $uri = $this->generateInternalUri($controller, $options['attributes'], $options['query']);
  99. $alt = '';
  100. if ($options['alt']) {
  101. $alt = $this->generateInternalUri($options['alt'][0], isset($options['alt'][1]) ? $options['alt'][1] : array(), isset($options['alt'][2]) ? $options['alt'][2] : array());
  102. }
  103. return $this->container->get('esi')->renderIncludeTag($uri, $alt, $options['ignore_errors'], $options['comment']);
  104. }
  105. $request = $this->container->get('request');
  106. // controller or URI?
  107. if (0 === strpos($controller, '/')) {
  108. $subRequest = Request::create($request->getUriForPath($controller), 'get', array(), $request->cookies->all(), array(), $request->server->all());
  109. if ($session = $request->getSession()) {
  110. $subRequest->setSession($session);
  111. }
  112. } else {
  113. $options['attributes']['_controller'] = $controller;
  114. if (!isset($options['attributes']['_format'])) {
  115. $options['attributes']['_format'] = $request->getRequestFormat();
  116. }
  117. $options['attributes']['_route'] = '_internal';
  118. $subRequest = $request->duplicate($options['query'], null, $options['attributes']);
  119. $subRequest->setMethod('GET');
  120. }
  121. $level = ob_get_level();
  122. try {
  123. $response = $this->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
  124. if (!$response->isSuccessful()) {
  125. throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $request->getUri(), $response->getStatusCode()));
  126. }
  127. return $response->getContent();
  128. } catch (\Exception $e) {
  129. if ($options['alt']) {
  130. $alt = $options['alt'];
  131. unset($options['alt']);
  132. $options['attributes'] = isset($alt[1]) ? $alt[1] : array();
  133. $options['query'] = isset($alt[2]) ? $alt[2] : array();
  134. return $this->render($alt[0], $options);
  135. }
  136. if (!$options['ignore_errors']) {
  137. throw $e;
  138. }
  139. // let's clean up the output buffers that were created by the sub-request
  140. while (ob_get_level() > $level) {
  141. ob_get_clean();
  142. }
  143. }
  144. }
  145. /**
  146. * Generates an internal URI for a given controller.
  147. *
  148. * This method uses the "_internal" route, which should be available.
  149. *
  150. * @param string $controller A controller name to execute (a string like BlogBundle:Post:index), or a relative URI
  151. * @param array $attributes An array of request attributes
  152. * @param array $query An array of request query parameters
  153. *
  154. * @return string An internal URI
  155. */
  156. public function generateInternalUri($controller, array $attributes = array(), array $query = array())
  157. {
  158. if (0 === strpos($controller, '/')) {
  159. return $controller;
  160. }
  161. $path = http_build_query($attributes, '', '&');
  162. $uri = $this->container->get('router')->generate('_internal', array(
  163. 'controller' => $controller,
  164. 'path' => $path ?: 'none',
  165. '_format' => $this->container->get('request')->getRequestFormat(),
  166. ));
  167. if ($queryString = http_build_query($query, '', '&')) {
  168. $uri .= '?'.$queryString;
  169. }
  170. return $uri;
  171. }
  172. }