From 9aaceb19eebef484ba954bd67965276babf0a9e6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 19 Dec 2012 08:54:53 +0100 Subject: [PATCH 01/13] moved the logic from HttpKernel in FrameworkBundle to the HttpKernel component --- .../Twig/Extension/HttpKernelExtension.php | 61 +++------ .../Extension/HttpKernelExtensionTest.php | 23 ++-- .../Compiler/HttpRenderingStrategyPass.php | 37 ++++++ .../FrameworkExtension.php | 1 + .../FrameworkBundle/FrameworkBundle.php | 2 + .../Bundle/FrameworkBundle/HttpKernel.php | 94 +------------ .../Resources/config/content_generator.xml | 48 +++++++ .../Resources/config/routing/proxy.xml | 8 ++ .../Resources/config/templating_php.xml | 2 +- .../Templating/Helper/ActionsHelper.php | 20 ++- .../TwigBundle/Resources/config/twig.xml | 3 +- .../Component/HttpFoundation/Request.php | 10 ++ .../Controller/ControllerReference.php | 31 +++++ .../EventListener/RouterProxyListener.php | 78 +++++++++++ .../HttpKernel/HttpContentRenderer.php | 124 ++++++++++++++++++ .../DefaultRenderingStrategy.php | 101 ++++++++++++++ .../EsiRenderingStrategy.php | 66 ++++++++++ .../GeneratorAwareRenderingStrategy.php | 75 +++++++++++ .../HIncludeRenderingStrategy.php | 96 ++++++++++++++ .../RenderingStrategyInterface.php | 38 ++++++ 20 files changed, 760 insertions(+), 158 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/proxy.xml create mode 100644 src/Symfony/Component/HttpKernel/Controller/ControllerReference.php create mode 100644 src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php create mode 100644 src/Symfony/Component/HttpKernel/HttpContentRenderer.php create mode 100644 src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php create mode 100644 src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php create mode 100644 src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php create mode 100644 src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php create mode 100644 src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index bdf882ef4ac41..a9bfaac44e254 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -11,83 +11,54 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\HttpContentRenderer; +use Symfony\Component\HttpKernel\Controller\ControllerReference; /** * Provides integration with the HttpKernel component. * * @author Fabien Potencier */ -class HttpKernelExtension extends \Twig_Extension implements EventSubscriberInterface +class HttpKernelExtension extends \Twig_Extension { - private $kernel; - private $request; + private $renderer; /** * Constructor. * - * @param HttpKernelInterface $kernel A HttpKernelInterface install + * @param HttpContentRenderer $kernel A HttpContentRenderer instance */ - public function __construct(HttpKernelInterface $kernel) + public function __construct(HttpContentRenderer $renderer) { - $this->kernel = $kernel; + $this->renderer = $renderer; } public function getFunctions() { return array( - 'render' => new \Twig_Function_Method($this, 'render', array('needs_environment' => true, 'is_safe' => array('html'))), + 'render' => new \Twig_Function_Method($this, 'render', array('is_safe' => array('html'))), + 'controller' => new \Twig_Function_Method($this, 'controller'), ); } /** * Renders a URI. * - * @param \Twig_Environment $twig A \Twig_Environment instance - * @param string $uri The URI to render + * @param string $uri A URI + * @param array $options An array of options * * @return string The Response content * - * @throws \RuntimeException + * @see Symfony\Component\HttpKernel\HttpContentRenderer::render() */ - public function render(\Twig_Environment $twig, $uri) + public function render($uri, $options = array()) { - if (null !== $this->request) { - $cookies = $this->request->cookies->all(); - $server = $this->request->server->all(); - } else { - $cookies = array(); - $server = array(); - } - - $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); - if (null !== $this->request && $this->request->getSession()) { - $subRequest->setSession($this->request->getSession()); - } - - $response = $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); - - if (!$response->isSuccessful()) { - throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); - } - - return $response->getContent(); + return $this->renderer->render($uri, $options); } - public function onKernelRequest(GetResponseEvent $event) + public function controller($controller, $attributes = array(), $query = array()) { - $this->request = $event->getRequest(); - } - - public static function getSubscribedEvents() - { - return array( - KernelEvents::REQUEST => array('onKernelRequest'), - ); + return new ControllerReference($controller, $attributes, $query); } public function getName() diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index f5390d31be1d9..dd6030ea54afc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -14,7 +14,7 @@ use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Tests\TestCase; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpContentRenderer; class HttpKernelExtensionTest extends TestCase { @@ -31,7 +31,7 @@ protected function setUp() public function testRenderWithoutMasterRequest() { - $kernel = $this->getKernel($this->returnValue(new Response('foo'))); + $kernel = $this->getHttpContentRenderer($this->returnValue('foo')); $this->assertEquals('foo', $this->renderTemplate($kernel)); } @@ -41,7 +41,7 @@ public function testRenderWithoutMasterRequest() */ public function testRenderWithError() { - $kernel = $this->getKernel($this->throwException(new \Exception('foo'))); + $kernel = $this->getHttpContentRenderer($this->throwException(new \Exception('foo'))); $loader = new \Twig_Loader_Array(array('index' => '{{ render("foo") }}')); $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); @@ -50,23 +50,20 @@ public function testRenderWithError() $this->renderTemplate($kernel); } - protected function getKernel($return) + protected function getHttpContentRenderer($return) { - $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); - $kernel - ->expects($this->once()) - ->method('handle') - ->will($return) - ; + $strategy = $this->getMock('Symfony\\Component\\HttpKernel\\RenderingStrategy\\RenderingStrategyInterface'); + $strategy->expects($this->once())->method('getName')->will($this->returnValue('default')); + $strategy->expects($this->once())->method('render')->will($return); - return $kernel; + return new HttpContentRenderer(array($strategy)); } - protected function renderTemplate(HttpKernelInterface $kernel, $template = '{{ render("foo") }}') + protected function renderTemplate(HttpContentRenderer $renderer, $template = '{{ render("foo") }}') { $loader = new \Twig_Loader_Array(array('index' => $template)); $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); - $twig->addExtension(new HttpKernelExtension($kernel)); + $twig->addExtension(new HttpKernelExtension($renderer)); return $twig->render('index'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php new file mode 100644 index 0000000000000..3d31def579b45 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Adds services tagged kernel.content_renderer_strategy as HTTP content rendering strategies. + * + * @author Fabien Potencier + */ +class HttpRenderingStrategyPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition('http_content_renderer')) { + return; + } + + $definition = $container->getDefinition('http_content_renderer'); + foreach (array_keys($container->findTaggedServiceIds('kernel.content_renderer_strategy')) as $id) { + $definition->addMethodCall('addStrategy', array(new Reference($id))); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index baeb0abba85fa..3de40ec1fae2d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -41,6 +41,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('web.xml'); $loader->load('services.xml'); + $loader->load('content_generator.xml'); // A translator must always be registered (as support is included by // default in the Form component). If disabled, an identity translator diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index aec13c4aab3b2..3f18da7892944 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -25,6 +25,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CompilerDebugDumpPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\HttpRenderingStrategyPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Scope; @@ -65,6 +66,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new AddCacheClearerPass()); $container->addCompilerPass(new TranslationExtractorPass()); $container->addCompilerPass(new TranslationDumperPass()); + $container->addCompilerPass(new HttpRenderingStrategyPass()); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index 4f2d2a19ecfb1..12dc5e80ff235 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -30,8 +30,6 @@ class HttpKernel extends BaseHttpKernel { protected $container; - private $esiSupport; - public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver) { parent::__construct($dispatcher, $controllerResolver); @@ -96,97 +94,13 @@ public function forward($controller, array $attributes = array(), array $query = * * @throws \RuntimeException * @throws \Exception - */ - public function render($uri, array $options = array()) - { - $request = $this->container->get('request'); - - $options = array_merge(array( - 'ignore_errors' => !$this->container->getParameter('kernel.debug'), - 'alt' => null, - 'standalone' => false, - 'comment' => '', - 'default' => null, - ), $options); - - if (null === $this->esiSupport) { - $this->esiSupport = $this->container->has('esi') && $this->container->get('esi')->hasSurrogateEsiCapability($request); - } - - if ($this->esiSupport && (true === $options['standalone'] || 'esi' === $options['standalone'])) { - return $this->container->get('esi')->renderIncludeTag($uri, $options['alt'], $options['ignore_errors'], $options['comment']); - } - - if ('js' === $options['standalone']) { - $defaultContent = null; - - $templating = $this->container->get('templating'); - - if ($options['default']) { - if ($templating->exists($options['default'])) { - $defaultContent = $templating->render($options['default']); - } else { - $defaultContent = $options['default']; - } - } elseif ($template = $this->container->getParameter('templating.hinclude.default_template')) { - $defaultContent = $templating->render($template); - } - - return $this->renderHIncludeTag($uri, $defaultContent); - } - - $subRequest = Request::create($uri, 'get', array(), $request->cookies->all(), array(), $request->server->all()); - if ($session = $request->getSession()) { - $subRequest->setSession($session); - } - - $level = ob_get_level(); - try { - $response = $this->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); - - if (!$response->isSuccessful()) { - throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $request->getUri(), $response->getStatusCode())); - } - - if (!$response instanceof StreamedResponse) { - return $response->getContent(); - } - - $response->sendContent(); - } catch (\Exception $e) { - if ($options['alt']) { - $alt = $options['alt']; - unset($options['alt']); - - return $this->render($alt, $options); - } - - if (!$options['ignore_errors']) { - throw $e; - } - - // let's clean up the output buffers that were created by the sub-request - while (ob_get_level() > $level) { - ob_get_clean(); - } - } - } - - /** - * Renders an HInclude tag. - * - * @param string $uri A URI - * @param string $defaultContent Default content * - * @return string + * @deprecated in 2.2, will be removed in 2.3 (use Symfony\Component\HttpKernel\HttpContentRenderer::render() instead) */ - public function renderHIncludeTag($uri, $defaultContent = null) + public function render($uri, array $options = array()) { - return sprintf('%s', $uri, $defaultContent); - } + trigger_error('render() is deprecated since version 2.2 and will be removed in 2.3. Use Symfony\Component\HttpKernel\HttpContentRenderer::render() instead.', E_USER_DEPRECATED); - public function hasEsiSupport() - { - return $this->esiSupport; + $this->container->get('http_content_renderer')->render($uri, $options); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml new file mode 100644 index 0000000000000..bb381281e6520 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml @@ -0,0 +1,48 @@ + + + + + + Symfony\Component\HttpKernel\HttpContentRenderer + Symfony\Component\HttpKernel\RenderingStrategy\DefaultRenderingStrategy + Symfony\Component\HttpKernel\RenderingStrategy\EsiRenderingStrategy + Symfony\Component\HttpKernel\RenderingStrategy\HIncludeRenderingStrategy + + Symfony\Component\HttpKernel\EventListener\RouterProxyListener + + + + + + + %kernel.debug% + + + + + + + + + + + + + + + + + + + %http_content_renderer.strategy.hinclude.global_template% + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/proxy.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/proxy.xml new file mode 100644 index 0000000000000..21ca146126f52 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/proxy.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml index ea1795455f80b..f93c7c7db4c21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml @@ -81,7 +81,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php index a4fdc3510ea99..cc5f486fa1128 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php @@ -12,7 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; use Symfony\Component\Templating\Helper\Helper; -use Symfony\Bundle\FrameworkBundle\HttpKernel; +use Symfony\Component\HttpKernel\HttpContentRenderer; +use Symfony\Component\HttpKernel\Controller\ControllerReference; /** * ActionsHelper manages action inclusions. @@ -21,16 +22,16 @@ */ class ActionsHelper extends Helper { - protected $kernel; + private $renderer; /** * Constructor. * - * @param HttpKernel $kernel A HttpKernel instance + * @param HttpContentRenderer $kernel A HttpContentRenderer instance */ - public function __construct(HttpKernel $kernel) + public function __construct(HttpContentRenderer $renderer) { - $this->kernel = $kernel; + $this->renderer = $renderer; } /** @@ -41,11 +42,16 @@ public function __construct(HttpKernel $kernel) * * @return string * - * @see Symfony\Bundle\FrameworkBundle\HttpKernel::render() + * @see Symfony\Component\HttpKernel\HttpContentRenderer::render() */ public function render($uri, array $options = array()) { - return $this->kernel->render($uri, $options); + return $this->renderer->render($uri, $options); + } + + public function controller($controller, $attributes = array(), $query = array()) + { + return new ControllerReference($controller, $attributes, $query); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index c12c0898b1323..2a106d6c3b22e 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -90,8 +90,7 @@ - - + diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 5ec5fd3510cad..9a647c43fc42f 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -486,6 +486,16 @@ public static function setTrustedProxies(array $proxies) self::$trustProxy = $proxies ? true : false; } + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies. + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + /** * Sets the name for trusted headers. * diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php b/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php new file mode 100644 index 0000000000000..90ac2ba41a448 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +/** + * ControllerReference. + * + * @author Fabien Potencier + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php new file mode 100644 index 0000000000000..bb471ae28b337 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Proxies URIs when the current route name is "_proxy". + * + * If the request does not come from a trusted, it throws an + * AccessDeniedHttpException exception. + * + * @author Fabien Potencier + */ +class RouterProxyListener implements EventSubscriberInterface +{ + /** + * Fixes request attributes when the route is '_proxy'. + * + * @param GetResponseEvent $event A GetResponseEvent instance + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ('_proxy' !== $request->attributes->get('_route')) { + return; + } + + $this->checkRequest($request); + + parse_str($request->query->get('path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params'), $attributes)); + $request->query->remove('path'); + } + + protected function checkRequest(Request $request) + { + $trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies()); + $remoteAddress = $request->server->get('REMOTE_ADDR'); + foreach ($trustedIps as $ip) { + if (IpUtils::checkIp($remoteAddress, $ip)) { + return; + } + } + + throw new AccessDeniedHttpException(); + } + + protected function getLocalIpAddresses() + { + return array('127.0.0.1', 'fe80::1', '::1'); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + ); + } +} diff --git a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php new file mode 100644 index 0000000000000..b62e0491c3518 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\RenderingStrategy\RenderingStrategyInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * + * @author Fabien Potencier + */ +class HttpContentRenderer implements EventSubscriberInterface +{ + private $debug; + private $strategies; + private $requests; + + public function __construct(array $strategies = array(), $debug = false) + { + $this->strategies = array(); + foreach ($strategies as $strategy) { + $this->addStrategy($strategy); + } + $this->debug = $debug; + $this->requests = array(); + } + + public function addStrategy(RenderingStrategyInterface $strategy) + { + $this->strategies[$strategy->getName()] = $strategy; + } + + /** + * Stores the Request object. + * + * @param GetResponseEvent $event A GetResponseEvent instance + */ + public function onKernelRequest(GetResponseEvent $event) + { + array_unshift($this->requests, $event->getRequest()); + } + + /** + * Removes the most recent Request object. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + array_shift($this->requests); + } + + /** + * Renders a URI and returns the Response content. + * + * * ignore_errors: true to return an empty string in case of an error + * * strategy: the strategy to use for rendering + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param array $options An array of options + * + * @return string The Response content + */ + public function render($uri, array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + $options = $this->fixOptions($options); + + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; + + if (!isset($this->strategies[$strategy])) { + throw new \InvalidArgumentException(sprintf('The "%s" rendering strategy does not exist.', $strategy)); + } + + return $this->strategies[$strategy]->render($uri, $this->requests ? $this->requests[0] : null, $options); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => 'onKernelRequest', + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } + + private function fixOptions($options) + { + // support for the standalone option is @deprecated in 2.2 and replaced with the strategy option + if (isset($options['standalone'])) { + trigger_error('The "standalone" option is deprecated in version 2.2 and replaced with the "strategy" option.', E_USER_DEPRECATED); + + // support for the true value is @deprecated in 2.2, will be removed in 2.3 + if (true === $options['standalone']) { + trigger_error('The "true" value for the "standalone" option is deprecated in version 2.2 and replaced with the "esi" value.', E_USER_DEPRECATED); + + $options['standalone'] = 'esi'; + } elseif ('js' === $options['standalone']) { + trigger_error('The "js" value for the "standalone" option is deprecated in version 2.2 and replaced with the "hinclude" value.', E_USER_DEPRECATED); + + $options['standalone'] = 'hinclude'; + } + + $options['strategy'] = $options['standalone']; + } + + return $options; + } +} diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php new file mode 100644 index 0000000000000..1b6d25236541e --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\RenderingStrategy; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * + * @author Fabien Potencier + */ +class DefaultRenderingStrategy extends GeneratorAwareRenderingStrategy +{ + private $kernel; + + public function __construct(HttpKernelInterface $kernel) + { + $this->kernel = $kernel; + } + + public function render($uri, Request $request = null, array $options = array()) + { + if ($uri instanceof ControllerReference) { + $uri = $this->generateProxyUri($uri, $request); + } + + $subRequest = $this->createSubRequest($uri, $request); + + $level = ob_get_level(); + try { + return $this->handle($subRequest); + } catch (\Exception $e) { + // let's clean up the output buffers that were created by the sub-request + while (ob_get_level() > $level) { + ob_get_clean(); + } + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + } + } + + protected function handle(Request $request) + { + $response = $this->kernel->handle($request, HttpKernelInterface::SUB_REQUEST, false); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $request->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } + + protected function createSubRequest($uri, Request $request = null) + { + if (null !== $request) { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + // the sub-request is internal + $server['REMOTE_ADDR'] = '127.0.0.1'; + } else { + $cookies = array(); + $server = array(); + } + + $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); + if (null !== $request && $session = $request->getSession()) { + $subRequest->setSession($session); + } + + return $subRequest; + } + + public function getName() + { + return 'default'; + } +} diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php new file mode 100644 index 0000000000000..be9bdb9351bb0 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\RenderingStrategy; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\Esi; + +/** + * + * @author Fabien Potencier + */ +class EsiRenderingStrategy extends GeneratorAwareRenderingStrategy +{ + private $esi; + private $defaultStrategy; + + public function __construct(Esi $esi, RenderingStrategyInterface $defaultStrategy) + { + $this->esi = $esi; + $this->defaultStrategy = $defaultStrategy; + } + + /** + * + * Note that this method generates an esi:include tag only when both the standalone + * option is set to true and the request has ESI capability (@see Symfony\Component\HttpKernel\HttpCache\ESI). + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * * alt: an alternative URI to execute in case of an error + * * comment: a comment to add when returning an esi:include tag + */ + public function render($uri, Request $request = null, array $options = array()) + { + if (!$this->esi->hasSurrogateEsiCapability($request)) { + return $this->defaultStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateProxyUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateProxyUri($alt, $request); + } + + return $this->esi->renderIncludeTag($uri, $alt, $options['ignore_errors'], isset($options['comment']) ? $options['comment'] : ''); + } + + public function getName() + { + return 'esi'; + } +} diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php new file mode 100644 index 0000000000000..7b5ecb5295275 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\RenderingStrategy; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; + +/** + * + * @author Fabien Potencier + */ +abstract class GeneratorAwareRenderingStrategy implements RenderingStrategyInterface +{ + protected $generator; + + public function setUrlGenerator(UrlGeneratorInterface $generator) + { + $this->generator = $generator; + } + + /** + * Generates a proxy URI for a given controller. + * + * This method only works when using the Symfony Routing component and + * if a "_proxy" route is defined with a {_controller} and {_format} + * placeholders. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * + * @return string A proxy URI + */ + protected function generateProxyUri(ControllerReference $reference, Request $request = null) + { + if (null === $this->generator) { + throw new \LogicException('Unable to generate a proxy URL as there is no registered route generator.'); + } + + if (isset($reference->attributes['_format'])) { + $format = $reference->attributes['_format']; + unset($reference->attributes['_format']); + } elseif (null !== $request) { + $format = $request->getRequestFormat(); + } else { + $format = 'html'; + } + + try { + $uri = $this->generator->generate('_proxy', array('_controller' => $reference->controller, '_format' => $format), true); + } catch (RouteNotFoundException $e) { + throw new \LogicException('Unable to generate a proxy URL as the "_proxy" route is not registered.', 0, $e); + } + + if ($path = http_build_query($reference->attributes, '', '&')) { + $reference->query['path'] = $path; + } + + if ($qs = http_build_query($reference->query, '', '&')) { + $uri .= '?'.$qs; + } + + return $uri; + } +} diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php new file mode 100644 index 0000000000000..ec1cbc2e2900c --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\RenderingStrategy; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * + * @author Fabien Potencier + */ +class HIncludeRenderingStrategy implements RenderingStrategyInterface +{ + private $templating; + private $globalDefaultTemplate; + + public function __construct($templating, $globalDefaultTemplate = null) + { + if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + $this->globalDefaultTemplate = $globalDefaultTemplate; + } + + public function render($uri, Request $request = null, array $options = array()) + { + if ($uri instanceof ControllerReference) { + // FIXME: can we sign the proxy URL instead? + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy.'); + } + + $defaultTemplate = $options['default'] ?: null; + $defaultContent = null; + + if (null !== $defaultTemplate) { + if ($this->templateExists($defaultTemplate)) { + $defaultContent = $this->templating->render($defaultContent); + } else { + $defaultContent = $defaultTemplate; + } + } elseif ($this->globalDefaultTemplate) { + $defaultContent = $this->templating->render($this->globalDefaultTemplate); + } + + return $this->renderHIncludeTag($uri, $defaultContent); + } + + /** + * Renders an HInclude tag. + * + * @param string $uri A URI + * @param string $defaultContent Default content + */ + protected function renderHIncludeTag($uri, $defaultContent = null) + { + return sprintf('%s', $uri, $defaultContent); + } + + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + return $this->templating->exists($template); + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (\Twig_Error_Loader $e) { + } + + return false; + } + + public function getName() + { + return 'hinclude'; + } +} diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php new file mode 100644 index 0000000000000..e38675144884d --- /dev/null +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\RenderingStrategy; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * + * @author Fabien Potencier + */ +interface RenderingStrategyInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + */ + public function render($uri, Request $request = null, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} From a0c49c3a94e8b5efa1e73eeda147ed1b1d8b06eb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 3 Jan 2013 18:51:16 +0100 Subject: [PATCH 02/13] [TwigBridge] added a render_* function to ease usage of custom rendering strategies Here is the code you need to write when using the regular render function for an ESI strategy: {{ render(path('path'), { strategy: 'esi' }) }} And the same with the new render_* function: {{ render_esi(path('path')) }} --- src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index a9bfaac44e254..14d5a5c8ad783 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -37,6 +37,7 @@ public function getFunctions() { return array( 'render' => new \Twig_Function_Method($this, 'render', array('is_safe' => array('html'))), + 'render_*' => new \Twig_Function_Method($this, 'renderStrategy', array('is_safe' => array('html'))), 'controller' => new \Twig_Function_Method($this, 'controller'), ); } @@ -56,6 +57,13 @@ public function render($uri, $options = array()) return $this->renderer->render($uri, $options); } + public function renderStrategy($strategy, $uri, $options = array()) + { + $options['strategy'] = $strategy; + + return $this->renderer->render($uri, $options); + } + public function controller($controller, $attributes = array(), $query = array()) { return new ControllerReference($controller, $attributes, $query); From 892f00ffeeb1e392e0fb0373dfd94b8abd3f194e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 3 Jan 2013 19:22:22 +0100 Subject: [PATCH 03/13] [HttpKernel] added a URL signer mechanism for hincludes --- .../Resources/config/content_generator.xml | 3 + .../Resources/config/services.xml | 5 ++ .../EventListener/RouterProxyListener.php | 23 +++++- .../HIncludeRenderingStrategy.php | 16 +++-- .../Component/HttpKernel/UriSigner.php | 72 +++++++++++++++++++ 5 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/UriSigner.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml index bb381281e6520..7dd51b127f436 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml @@ -36,12 +36,15 @@ + %http_content_renderer.strategy.hinclude.global_template% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 36f01f09e4218..fbddc0e07fbfd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -11,6 +11,7 @@ Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer Symfony\Component\HttpKernel\Config\FileLocator + Symfony\Component\HttpKernel\UriSigner @@ -51,5 +52,9 @@ %kernel.root_dir%/Resources + + + %kernel.secret% + diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php index bb471ae28b337..ff339dc020015 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\UriSigner; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -28,6 +29,13 @@ */ class RouterProxyListener implements EventSubscriberInterface { + private $signer; + + public function __construct(UriSigner $signer) + { + $this->signer = $signer; + } + /** * Fixes request attributes when the route is '_proxy'. * @@ -43,7 +51,7 @@ public function onKernelRequest(GetResponseEvent $event) return; } - $this->checkRequest($request); + $this->validateRequest($request); parse_str($request->query->get('path', ''), $attributes); $request->attributes->add($attributes); @@ -51,8 +59,14 @@ public function onKernelRequest(GetResponseEvent $event) $request->query->remove('path'); } - protected function checkRequest(Request $request) + protected function validateRequest(Request $request) { + // is the Request safe? + if (!$request->isMethodSafe()) { + throw new AccessDeniedHttpException(); + } + + // does the Request come from a trusted IP? $trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies()); $remoteAddress = $request->server->get('REMOTE_ADDR'); foreach ($trustedIps as $ip) { @@ -61,6 +75,11 @@ protected function checkRequest(Request $request) } } + // is the Request signed? + if ($this->signer->check($request->getUri())) { + return; + } + throw new AccessDeniedHttpException(); } diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php index ec1cbc2e2900c..b91a3b8800d51 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php @@ -14,17 +14,19 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Templating\EngineInterface; use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; /** * * @author Fabien Potencier */ -class HIncludeRenderingStrategy implements RenderingStrategyInterface +class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy { private $templating; private $globalDefaultTemplate; + private $signer; - public function __construct($templating, $globalDefaultTemplate = null) + public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null) { if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface'); @@ -32,16 +34,20 @@ public function __construct($templating, $globalDefaultTemplate = null) $this->templating = $templating; $this->globalDefaultTemplate = $globalDefaultTemplate; + $this->signer = $signer; } public function render($uri, Request $request = null, array $options = array()) { if ($uri instanceof ControllerReference) { - // FIXME: can we sign the proxy URL instead? - throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy.'); + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + $uri = $this->signer->sign($this->generateProxyUri($uri, $request)); } - $defaultTemplate = $options['default'] ?: null; + $defaultTemplate = isset($options['default']) ? $options['default'] : null; $defaultContent = null; if (null !== $defaultTemplate) { diff --git a/src/Symfony/Component/HttpKernel/UriSigner.php b/src/Symfony/Component/HttpKernel/UriSigner.php new file mode 100644 index 0000000000000..2ff6b9b2aab4d --- /dev/null +++ b/src/Symfony/Component/HttpKernel/UriSigner.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * UriSigner. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + + /** + * Constructor. + * + * @param string $secret A secret + */ + public function __construct($secret) + { + $this->secret = $secret; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding a _hash query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + return $uri.(false === (strpos($uri, '?')) ? '?' : '&').'_hash='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * @param string $uri A signed URI + * + * @return Boolean True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + if (!preg_match('/(\?|&)_hash=(.+?)(&|$)/', $uri, $matches, PREG_OFFSET_CAPTURE)) { + return false; + } + + // the naked URI is the URI without the _hash parameter (we need to keep the ? if there is some other parameters after) + $offset = ('?' == $matches[1][0] && '&' != $matches[3][0]) ? 0 : 1; + $nakedUri = substr($uri, 0, $matches[0][1] + $offset).substr($uri, $matches[0][1] + strlen($matches[0][0])); + + return $this->computeHash($nakedUri) === $matches[2][0]; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha1', $uri, $this->secret, true))); + } +} From 403bb060ce45cc48df9b0ff299ba8f2aa53de21a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 4 Jan 2013 18:38:08 +0100 Subject: [PATCH 04/13] [HttpKernel] added missing phpdoc and tweaked existing ones --- .../Controller/ControllerReference.php | 16 +++++++++++- .../EventListener/RouterProxyListener.php | 2 +- .../HttpKernel/HttpContentRenderer.php | 18 ++++++++++++- .../DefaultRenderingStrategy.php | 12 +++++++++ .../EsiRenderingStrategy.php | 26 +++++++++++++++---- .../GeneratorAwareRenderingStrategy.php | 6 +++++ .../HIncludeRenderingStrategy.php | 14 ++++++++++ .../RenderingStrategyInterface.php | 8 ++++++ .../Component/HttpKernel/UriSigner.php | 2 +- 9 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php b/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php index 90ac2ba41a448..905e89f5dc00b 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerReference.php @@ -12,9 +12,16 @@ namespace Symfony\Component\HttpKernel\Controller; /** - * ControllerReference. + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. * * @author Fabien Potencier + * + * @see Symfony\Component\HttpKernel\HttpContentRenderer + * @see Symfony\Component\HttpKernel\RenderingStrategy\RenderingStrategyInterface */ class ControllerReference { @@ -22,6 +29,13 @@ class ControllerReference public $attributes = array(); public $query = array(); + /** + * Constructor. + * + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ public function __construct($controller, array $attributes = array(), array $query = array()) { $this->controller = $controller; diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php index ff339dc020015..05e8717fc0f73 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php @@ -22,7 +22,7 @@ /** * Proxies URIs when the current route name is "_proxy". * - * If the request does not come from a trusted, it throws an + * If the request does not come from a trusted IP, it throws an * AccessDeniedHttpException exception. * * @author Fabien Potencier diff --git a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php index b62e0491c3518..5344917ee15c3 100644 --- a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php +++ b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php @@ -19,6 +19,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** + * Renders a URI using different strategies. * * @author Fabien Potencier */ @@ -28,6 +29,12 @@ class HttpContentRenderer implements EventSubscriberInterface private $strategies; private $requests; + /** + * Constructor. + * + * @param RenderingStrategyInterface[] $strategies An array of RenderingStrategyInterface instances + * @param Boolean $debug Whether the debug mode is enabled or not + */ public function __construct(array $strategies = array(), $debug = false) { $this->strategies = array(); @@ -38,6 +45,11 @@ public function __construct(array $strategies = array(), $debug = false) $this->requests = array(); } + /** + * Adds a rendering strategy. + * + * @param RenderingStrategyInterface $strategy A RenderingStrategyInterface instance + */ public function addStrategy(RenderingStrategyInterface $strategy) { $this->strategies[$strategy->getName()] = $strategy; @@ -66,13 +78,16 @@ public function onKernelResponse(FilterResponseEvent $event) /** * Renders a URI and returns the Response content. * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * * * ignore_errors: true to return an empty string in case of an error * * strategy: the strategy to use for rendering * * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * @param array $options An array of options * - * @return string The Response content + * @return string|null The Response content or null when the Response is streamed */ public function render($uri, array $options = array()) { @@ -99,6 +114,7 @@ public static function getSubscribedEvents() ); } + // to be removed in 2.3 private function fixOptions($options) { // support for the standalone option is @deprecated in 2.2 and replaced with the strategy option diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php index 1b6d25236541e..5198c01ba9729 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference; /** + * Implements the default rendering strategy where the Request is rendered by the current HTTP kernel. * * @author Fabien Potencier */ @@ -23,11 +24,19 @@ class DefaultRenderingStrategy extends GeneratorAwareRenderingStrategy { private $kernel; + /** + * Constructor. + * + * @param HttpKernelInterface $kernel A HttpKernelInterface instance + */ public function __construct(HttpKernelInterface $kernel) { $this->kernel = $kernel; } + /** + * {@inheritdoc} + */ public function render($uri, Request $request = null, array $options = array()) { if ($uri instanceof ControllerReference) { @@ -94,6 +103,9 @@ protected function createSubRequest($uri, Request $request = null) return $subRequest; } + /** + * {@inheritdoc} + */ public function getName() { return 'default'; diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php index be9bdb9351bb0..cf9f5720796aa 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\HttpCache\Esi; /** + * Implements the ESI rendering strategy. * * @author Fabien Potencier */ @@ -24,6 +25,16 @@ class EsiRenderingStrategy extends GeneratorAwareRenderingStrategy private $esi; private $defaultStrategy; + /** + * Constructor. + * + * The "fallback" strategy when ESI is not available should always be an + * instance of DefaultRenderingStrategy (or a class you are using for the + * default strategy). + * + * @param Esi $esi An Esi instance + * @param RenderingStrategyInterface $defaultStrategy The default strategy to use when ESI is not supported + */ public function __construct(Esi $esi, RenderingStrategyInterface $defaultStrategy) { $this->esi = $esi; @@ -31,15 +42,17 @@ public function __construct(Esi $esi, RenderingStrategyInterface $defaultStrateg } /** + * {@inheritdoc} * - * Note that this method generates an esi:include tag only when both the standalone - * option is set to true and the request has ESI capability (@see Symfony\Component\HttpKernel\HttpCache\ESI). + * Note that if the current Request has no ESI capability, this method + * falls back to use the default rendering strategy. * - * Available options: + * Additional available options: * - * * ignore_errors: true to return an empty string in case of an error - * * alt: an alternative URI to execute in case of an error + * * alt: an alternative URI to render in case of an error * * comment: a comment to add when returning an esi:include tag + * + * @see Symfony\Component\HttpKernel\HttpCache\ESI */ public function render($uri, Request $request = null, array $options = array()) { @@ -59,6 +72,9 @@ public function render($uri, Request $request = null, array $options = array()) return $this->esi->renderIncludeTag($uri, $alt, $options['ignore_errors'], isset($options['comment']) ? $options['comment'] : ''); } + /** + * {@inheritdoc} + */ public function getName() { return 'esi'; diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php index 7b5ecb5295275..408d682326811 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php @@ -17,6 +17,7 @@ use Symfony\Component\Routing\Exception\RouteNotFoundException; /** + * Adds the possibility to generate a proxy URI for a given Controller. * * @author Fabien Potencier */ @@ -24,6 +25,11 @@ abstract class GeneratorAwareRenderingStrategy implements RenderingStrategyInter { protected $generator; + /** + * Sets a URL generator to use for proxy URIs generation. + * + * @param UrlGeneratorInterface $generator An UrlGeneratorInterface instance + */ public function setUrlGenerator(UrlGeneratorInterface $generator) { $this->generator = $generator; diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php index b91a3b8800d51..1a67cbe45cf69 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpKernel\UriSigner; /** + * Implements the Hinclude rendering strategy. * * @author Fabien Potencier */ @@ -26,6 +27,13 @@ class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy private $globalDefaultTemplate; private $signer; + /** + * Constructor. + * + * @param EngineInterface|\Twig_Environment $templating An EngineInterface or a \Twig_Environment instance + * @param UriSigner $signer A UriSigner instance + * @param string $globalDefaultTemplate The content of the global default template + */ public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null) { if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { @@ -37,6 +45,9 @@ public function __construct($templating, UriSigner $signer = null, $globalDefaul $this->signer = $signer; } + /** + * {@inheritdoc} + */ public function render($uri, Request $request = null, array $options = array()) { if ($uri instanceof ControllerReference) { @@ -95,6 +106,9 @@ private function templateExists($template) return false; } + /** + * {@inheritdoc} + */ public function getName() { return 'hinclude'; diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php index e38675144884d..36419c3792f94 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/RenderingStrategyInterface.php @@ -15,17 +15,25 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference; /** + * Interface implemented by all rendering strategies. * * @author Fabien Potencier + * + * @see Symfony\Component\HttpKernel\HttpContentRenderer */ interface RenderingStrategyInterface { /** * Renders a URI and returns the Response content. * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * @param Request $request A Request instance * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed */ public function render($uri, Request $request = null, array $options = array()); diff --git a/src/Symfony/Component/HttpKernel/UriSigner.php b/src/Symfony/Component/HttpKernel/UriSigner.php index 2ff6b9b2aab4d..3530c31a77e7c 100644 --- a/src/Symfony/Component/HttpKernel/UriSigner.php +++ b/src/Symfony/Component/HttpKernel/UriSigner.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel; /** - * UriSigner. + * Signs URIs. * * @author Fabien Potencier */ From 1f1392dc8bce232a3111a08e1627884d77c69901 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 4 Jan 2013 18:47:22 +0100 Subject: [PATCH 05/13] [HttpKernel] simplified and enhanced code managing the hinclude strategy --- .../HIncludeRenderingStrategy.php | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php index 1a67cbe45cf69..7a4b77fc2face 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php @@ -32,7 +32,7 @@ class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy * * @param EngineInterface|\Twig_Environment $templating An EngineInterface or a \Twig_Environment instance * @param UriSigner $signer A UriSigner instance - * @param string $globalDefaultTemplate The content of the global default template + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) */ public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null) { @@ -47,6 +47,10 @@ public function __construct($templating, UriSigner $signer = null, $globalDefaul /** * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) */ public function render($uri, Request $request = null, array $options = array()) { @@ -58,31 +62,14 @@ public function render($uri, Request $request = null, array $options = array()) $uri = $this->signer->sign($this->generateProxyUri($uri, $request)); } - $defaultTemplate = isset($options['default']) ? $options['default'] : null; - $defaultContent = null; - - if (null !== $defaultTemplate) { - if ($this->templateExists($defaultTemplate)) { - $defaultContent = $this->templating->render($defaultContent); - } else { - $defaultContent = $defaultTemplate; - } - } elseif ($this->globalDefaultTemplate) { - $defaultContent = $this->templating->render($this->globalDefaultTemplate); + $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; + if ($this->templateExists($template)) { + $content = $this->templating->render($template); + } else { + $content = $template; } - return $this->renderHIncludeTag($uri, $defaultContent); - } - - /** - * Renders an HInclude tag. - * - * @param string $uri A URI - * @param string $defaultContent Default content - */ - protected function renderHIncludeTag($uri, $defaultContent = null) - { - return sprintf('%s', $uri, $defaultContent); + return sprintf('%s', $uri, $content); } private function templateExists($template) From adc067e9386a87f4987275ede03833b74922adeb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Jan 2013 08:59:41 +0100 Subject: [PATCH 06/13] [FrameworkBundle] made some services private --- .../FrameworkBundle/Resources/config/content_generator.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml index 7dd51b127f436..1e6dc5c6c1d5a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml @@ -20,19 +20,20 @@ %kernel.debug% - + - + + From 1240690cacd5a9687ed988104cb33201864adf72 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Jan 2013 09:31:07 +0100 Subject: [PATCH 07/13] [HttpKernel] made the strategy a regular parameter in HttpContentRenderer::render() --- .../Bridge/Twig/Extension/HttpKernelExtension.php | 9 +++++---- src/Symfony/Bundle/FrameworkBundle/HttpKernel.php | 5 ++++- .../Templating/Helper/ActionsHelper.php | 5 ++++- .../Component/HttpKernel/HttpContentRenderer.php | 15 +++++++++------ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index 14d5a5c8ad783..22ded3d1976ab 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -54,14 +54,15 @@ public function getFunctions() */ public function render($uri, $options = array()) { - return $this->renderer->render($uri, $options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; + unset($options['strategy']); + + return $this->renderer->render($uri, $strategy, $options); } public function renderStrategy($strategy, $uri, $options = array()) { - $options['strategy'] = $strategy; - - return $this->renderer->render($uri, $options); + return $this->renderer->render($uri, $strategy, $options); } public function controller($controller, $attributes = array(), $query = array()) diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index 12dc5e80ff235..812784b73717c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -101,6 +101,9 @@ public function render($uri, array $options = array()) { trigger_error('render() is deprecated since version 2.2 and will be removed in 2.3. Use Symfony\Component\HttpKernel\HttpContentRenderer::render() instead.', E_USER_DEPRECATED); - $this->container->get('http_content_renderer')->render($uri, $options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; + unset($options['strategy']); + + $this->container->get('http_content_renderer')->render($uri, $strategy, $options); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php index cc5f486fa1128..94c76982942bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php @@ -46,7 +46,10 @@ public function __construct(HttpContentRenderer $renderer) */ public function render($uri, array $options = array()) { - return $this->renderer->render($uri, $options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; + unset($options['strategy']); + + return $this->renderer->render($uri, $strategy, $options); } public function controller($controller, $attributes = array(), $query = array()) diff --git a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php index 5344917ee15c3..94e35bb15d816 100644 --- a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php +++ b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php @@ -81,23 +81,26 @@ public function onKernelResponse(FilterResponseEvent $event) * When the Response is a StreamedResponse, the content is streamed immediately * instead of being returned. * + * Available options: + * * * ignore_errors: true to return an empty string in case of an error - * * strategy: the strategy to use for rendering * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param array $options An array of options + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $strategy The strategy to use for the rendering + * @param array $options An array of options * * @return string|null The Response content or null when the Response is streamed */ - public function render($uri, array $options = array()) + public function render($uri, $strategy = 'default', array $options = array()) { if (!isset($options['ignore_errors'])) { $options['ignore_errors'] = !$this->debug; } $options = $this->fixOptions($options); - - $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; + if (isset($options['strategy'])) { + $strategy = $options['strategy']; + } if (!isset($this->strategies[$strategy])) { throw new \InvalidArgumentException(sprintf('The "%s" rendering strategy does not exist.', $strategy)); From a8ea4e4b10fca40b6283f6a7ec29fb62062cf2bd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Jan 2013 09:48:46 +0100 Subject: [PATCH 08/13] [FrameworkBundle] deprecated HttpKernel::forward() (it is only used once now and not part of any interface anyway) --- src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php | 5 ++++- src/Symfony/Bundle/FrameworkBundle/HttpKernel.php | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index a19030e11b4de..0196058642d0a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -59,7 +59,10 @@ public function generateUrl($route, $parameters = array(), $referenceType = UrlG */ public function forward($controller, array $path = array(), array $query = array()) { - return $this->container->get('http_kernel')->forward($controller, $path, $query); + $path['_controller'] = $controller; + $subRequest = $this->container->get('request')->duplicate($query, null, $path); + + return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index 812784b73717c..b67f0c640ea97 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -65,9 +65,13 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ * @param array $query An array of request query parameters * * @return Response A Response instance + * + * @deprecated in 2.2, will be removed in 2.3 */ public function forward($controller, array $attributes = array(), array $query = array()) { + trigger_error('forward() is deprecated since version 2.2 and will be removed in 2.3.', E_USER_DEPRECATED); + $attributes['_controller'] = $controller; $subRequest = $this->container->get('request')->duplicate($query, null, $attributes); From bd102c5eba3ba7c0d432ae11b248762f27ab94d6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Jan 2013 10:59:36 +0100 Subject: [PATCH 09/13] made the content renderer work even when ESI is disabled or when no templating engine is available (the latter being mostly useful when testing) --- .../Resources/config/content_generator.xml | 10 +--------- .../Bundle/FrameworkBundle/Resources/config/esi.xml | 8 ++++++++ .../RenderingStrategy/HIncludeRenderingStrategy.php | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml index 1e6dc5c6c1d5a..533e886fb6c13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml @@ -7,7 +7,6 @@ Symfony\Component\HttpKernel\HttpContentRenderer Symfony\Component\HttpKernel\RenderingStrategy\DefaultRenderingStrategy - Symfony\Component\HttpKernel\RenderingStrategy\EsiRenderingStrategy Symfony\Component\HttpKernel\RenderingStrategy\HIncludeRenderingStrategy Symfony\Component\HttpKernel\EventListener\RouterProxyListener @@ -26,17 +25,10 @@ - - - - - - - - + %http_content_renderer.strategy.hinclude.global_template% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml index 3038f40e97b46..45e9265442543 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml @@ -7,6 +7,7 @@ Symfony\Component\HttpKernel\HttpCache\Esi Symfony\Component\HttpKernel\EventListener\EsiListener + Symfony\Component\HttpKernel\RenderingStrategy\EsiRenderingStrategy @@ -16,5 +17,12 @@ + + + + + + + diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php index 7a4b77fc2face..82abf3f71f248 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/HIncludeRenderingStrategy.php @@ -34,9 +34,9 @@ class HIncludeRenderingStrategy extends GeneratorAwareRenderingStrategy * @param UriSigner $signer A UriSigner instance * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) */ - public function __construct($templating, UriSigner $signer = null, $globalDefaultTemplate = null) + public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null) { - if (!$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface'); } @@ -63,7 +63,7 @@ public function render($uri, Request $request = null, array $options = array()) } $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; - if ($this->templateExists($template)) { + if (null !== $this->templating && $this->templateExists($template)) { $content = $this->templating->render($template); } else { $content = $template; From 2eea7682e76e03aeb08064cba1c8a70c29f7ba0d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Jan 2013 11:00:55 +0100 Subject: [PATCH 10/13] moved the deprecation logic calls outside the new HttpContentRenderer class --- src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php | 2 ++ src/Symfony/Bundle/FrameworkBundle/HttpKernel.php | 2 ++ .../FrameworkBundle/Templating/Helper/ActionsHelper.php | 2 ++ src/Symfony/Component/HttpKernel/HttpContentRenderer.php | 8 ++------ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index 22ded3d1976ab..2e406fc1d94d3 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -54,6 +54,8 @@ public function getFunctions() */ public function render($uri, $options = array()) { + $options = $this->renderer->fixOptions($options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; unset($options['strategy']); diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index b67f0c640ea97..db4c3c3fc8e0c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -105,6 +105,8 @@ public function render($uri, array $options = array()) { trigger_error('render() is deprecated since version 2.2 and will be removed in 2.3. Use Symfony\Component\HttpKernel\HttpContentRenderer::render() instead.', E_USER_DEPRECATED); + $options = $this->renderer->fixOptions($options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; unset($options['strategy']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php index 94c76982942bd..abce1d10bf8f5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/ActionsHelper.php @@ -46,6 +46,8 @@ public function __construct(HttpContentRenderer $renderer) */ public function render($uri, array $options = array()) { + $options = $this->renderer->fixOptions($options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'default'; unset($options['strategy']); diff --git a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php index 94e35bb15d816..408dffc991cd8 100644 --- a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php +++ b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php @@ -97,11 +97,6 @@ public function render($uri, $strategy = 'default', array $options = array()) $options['ignore_errors'] = !$this->debug; } - $options = $this->fixOptions($options); - if (isset($options['strategy'])) { - $strategy = $options['strategy']; - } - if (!isset($this->strategies[$strategy])) { throw new \InvalidArgumentException(sprintf('The "%s" rendering strategy does not exist.', $strategy)); } @@ -118,7 +113,7 @@ public static function getSubscribedEvents() } // to be removed in 2.3 - private function fixOptions($options) + public function fixOptions(array $options) { // support for the standalone option is @deprecated in 2.2 and replaced with the strategy option if (isset($options['standalone'])) { @@ -136,6 +131,7 @@ private function fixOptions($options) } $options['strategy'] = $options['standalone']; + unset($options['standalone']); } return $options; From f17f5867a876772021aeb769e38b05f8ee497f07 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Jan 2013 11:02:31 +0100 Subject: [PATCH 11/13] moved the container aware HTTP kernel to the HttpKernel component --- .../Bundle/FrameworkBundle/HttpKernel.php | 40 +--------- .../ContainerAwareHttpKernel.php | 68 +++++++++++++++++ .../ContainerAwareHttpKernelTest.php} | 74 +++++-------------- .../DefaultRenderingStrategyTest.php | 64 ++++++++++++++++ 4 files changed, 156 insertions(+), 90 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php rename src/Symfony/{Bundle/FrameworkBundle/Tests/HttpKernelTest.php => Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php} (66%) create mode 100644 src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index db4c3c3fc8e0c..71a677cebeb0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -11,52 +11,20 @@ namespace Symfony\Bundle\FrameworkBundle; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\HttpKernel as BaseHttpKernel; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel; /** * This HttpKernel is used to manage scope changes of the DI container. * * @author Fabien Potencier * @author Johannes M. Schmitt + * + * @deprecated This class is deprecated in 2.2 and will be removed in 2.3 */ -class HttpKernel extends BaseHttpKernel +class HttpKernel extends ContainerAwareHttpKernel { - protected $container; - - public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver) - { - parent::__construct($dispatcher, $controllerResolver); - - $this->container = $container; - } - - public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) - { - $request->headers->set('X-Php-Ob-Level', ob_get_level()); - - $this->container->enterScope('request'); - $this->container->set('request', $request, 'request'); - - try { - $response = parent::handle($request, $type, $catch); - } catch (\Exception $e) { - $this->container->leaveScope('request'); - - throw $e; - } - - $this->container->leaveScope('request'); - - return $response; - } - /** * Forwards the request to another controller. * diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php new file mode 100644 index 0000000000000..20b4a5e75e9ad --- /dev/null +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * This HttpKernel is used to manage scope changes of the DI container. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ContainerAwareHttpKernel extends HttpKernel +{ + protected $container; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ContainerInterface $container A ContainerInterface instance + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver) + { + parent::__construct($dispatcher, $controllerResolver); + + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + $this->container->enterScope('request'); + $this->container->set('request', $request, 'request'); + + try { + $response = parent::handle($request, $type, $catch); + } catch (\Exception $e) { + $this->container->leaveScope('request'); + + throw $e; + } + + $this->container->leaveScope('request'); + + return $response; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/HttpKernelTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php similarity index 66% rename from src/Symfony/Bundle/FrameworkBundle/Tests/HttpKernelTest.php rename to src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php index c7587f6b4b941..80d5ffa61a664 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/HttpKernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php @@ -9,16 +9,31 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests; +namespace Symfony\Component\HttpKernel\Tests; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; -use Symfony\Bundle\FrameworkBundle\HttpKernel; use Symfony\Component\EventDispatcher\EventDispatcher; -class HttpKernelTest extends \PHPUnit_Framework_TestCase +class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase { + protected function setUp() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + /** * @dataProvider getProviderTypes */ @@ -46,7 +61,7 @@ public function testHandle($type) $dispatcher = new EventDispatcher(); $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); - $kernel = new HttpKernel($dispatcher, $container, $resolver); + $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver); $controller = function() use ($expected) { return $expected; @@ -93,7 +108,7 @@ public function testHandleRestoresThePreviousRequestOnException($type) $dispatcher = new EventDispatcher(); $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); - $kernel = new HttpKernel($dispatcher, $container, $resolver); + $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver); $controller = function() use ($expected) { throw $expected; @@ -123,53 +138,4 @@ public function getProviderTypes() array(HttpKernelInterface::SUB_REQUEST), ); } - - public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() - { - $request = new Request(); - - $container = $this->getMock('Symfony\\Component\\DependencyInjection\\ContainerInterface'); - $container - ->expects($this->at(0)) - ->method('get') - ->with($this->equalTo('request')) - ->will($this->returnValue($request)) - ; - $container - ->expects($this->at(1)) - ->method('getParameter') - ->with($this->equalTo('kernel.debug')) - ->will($this->returnValue(false)) - ; - $container - ->expects($this->at(2)) - ->method('has') - ->with($this->equalTo('esi')) - ->will($this->returnValue(false)) - ; - - $dispatcher = new EventDispatcher(); - $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); - $resolver->expects($this->once()) - ->method('getController') - ->will($this->returnValue(function () { - ob_start(); - echo 'bar'; - throw new \RuntimeException(); - })); - $resolver->expects($this->once()) - ->method('getArguments') - ->will($this->returnValue(array())); - - $kernel = new HttpKernel($dispatcher, $container, $resolver); - - // simulate a main request with output buffering - ob_start(); - echo 'Foo'; - - // simulate a sub-request with output buffering and an exception - $kernel->render('/'); - - $this->assertEquals('Foo', ob_get_clean()); - } } diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php new file mode 100644 index 0000000000000..48a545147092e --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\RenderingStrategy\DefaultRenderingStrategy; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class DefaultRenderingStrategyTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + $resolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver); + $renderer = new DefaultRenderingStrategy($kernel); + + // simulate a main request with output buffering + ob_start(); + echo 'Foo'; + + // simulate a sub-request with output buffering and an exception + $renderer->render('/', Request::create('/'), array('ignore_errors' => true)); + + $this->assertEquals('Foo', ob_get_clean()); + } +} From f7da1f0eb83f549c2245a3d2d1036be0688c78c6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 10 Jan 2013 16:08:44 +0100 Subject: [PATCH 12/13] added some unit tests (and fixed some bugs) --- .../Extension/HttpKernelExtensionTest.php | 1 - .../Compiler/HttpRenderingStrategyPass.php | 10 +- .../FrameworkBundle/FrameworkBundle.php | 2 +- .../Resources/config/content_generator.xml | 3 +- .../FrameworkBundle/Resources/config/esi.xml | 2 +- .../HttpRenderingStrategyPassTest.php | 105 ++++++++++++++++ .../EventListener/ValidationListenerTest.php | 1 - .../Component/HttpFoundation/Request.php | 3 +- .../EventListener/RouterProxyListener.php | 5 +- .../HttpKernel/HttpContentRenderer.php | 2 + .../DefaultRenderingStrategy.php | 4 + .../EsiRenderingStrategy.php | 4 +- .../GeneratorAwareRenderingStrategy.php | 5 +- .../EventListener/RouterProxyListenerTest.php | 119 ++++++++++++++++++ .../Tests/HttpContentRendererTest.php | 75 +++++++++++ .../AbstractRenderingStrategyTest.php | 29 +++++ .../DefaultRenderingStrategyTest.php | 62 ++++++++- .../EsiRenderingStrategyTest.php | 68 ++++++++++ .../GeneratorAwareRenderingStrategyTest.php | 101 +++++++++++++++ .../HIncludeRenderingStrategyTest.php | 71 +++++++++++ .../HttpKernel/Tests/UriSignerTest.php | 37 ++++++ .../Component/HttpKernel/UriSigner.php | 9 +- 22 files changed, 697 insertions(+), 21 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/HttpRenderingStrategyPassTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/EventListener/RouterProxyListenerTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/HttpContentRendererTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/AbstractRenderingStrategyTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/EsiRenderingStrategyTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/GeneratorAwareRenderingStrategyTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/HIncludeRenderingStrategyTest.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index dd6030ea54afc..182c42d07715e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -13,7 +13,6 @@ use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Tests\TestCase; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpContentRenderer; class HttpKernelExtensionTest extends TestCase diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php index 3d31def579b45..3bb16f28c7036 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/HttpRenderingStrategyPass.php @@ -14,7 +14,6 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Exception\LogicException; /** * Adds services tagged kernel.content_renderer_strategy as HTTP content rendering strategies. @@ -31,6 +30,15 @@ public function process(ContainerBuilder $container) $definition = $container->getDefinition('http_content_renderer'); foreach (array_keys($container->findTaggedServiceIds('kernel.content_renderer_strategy')) as $id) { + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\HttpKernel\RenderingStrategy\RenderingStrategyInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + $definition->addMethodCall('addStrategy', array(new Reference($id))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 3f18da7892944..c4caaea4e988a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -66,7 +66,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new AddCacheClearerPass()); $container->addCompilerPass(new TranslationExtractorPass()); $container->addCompilerPass(new TranslationDumperPass()); - $container->addCompilerPass(new HttpRenderingStrategyPass()); + $container->addCompilerPass(new HttpRenderingStrategyPass(), PassConfig::TYPE_AFTER_REMOVING); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml index 533e886fb6c13..332cf0de38804 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/content_generator.xml @@ -19,13 +19,12 @@ %kernel.debug% - + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml index 45e9265442543..0c4a271863a8d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/esi.xml @@ -18,7 +18,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/HttpRenderingStrategyPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/HttpRenderingStrategyPassTest.php new file mode 100644 index 0000000000000..4fe461fec4bf8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/HttpRenderingStrategyPassTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\HttpRenderingStrategyPass; + +class HttpRenderingStrategyPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests that content rendering not implementing RenderingStrategyInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testContentRendererWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_content_renderer' => array(), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.content_renderer_strategy here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $pass = new HttpRenderingStrategyPass(); + $pass->process($builder); + } + + public function testValidContentRenderer() + { + $services = array( + 'my_content_renderer' => array(), + ); + + $renderer = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $renderer + ->expects($this->once()) + ->method('addMethodCall') + ->with('addStrategy', array(new Reference('my_content_renderer'))) + ; + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\RenderingStrategyService')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.content_renderer_strategy here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->onConsecutiveCalls($renderer, $definition)); + + $pass = new HttpRenderingStrategyPass(); + $pass->process($builder); + } +} + +class RenderingStrategyService implements \Symfony\Component\HttpKernel\RenderingStrategy\RenderingStrategyInterface +{ + public function render($uri, Request $request = null, array $options = array()) + { + } + + public function getName() + { + return 'test'; + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/ValidationListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/ValidationListenerTest.php index d9555e13e1d54..3a8f7f41f0d7e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/ValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/ValidationListenerTest.php @@ -13,7 +13,6 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilder; -use Symfony\Component\Form\FormError; use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Extension\Validator\Constraints\Form; use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener; diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 9a647c43fc42f..0bac66ada759e 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -926,8 +926,7 @@ public function getSchemeAndHttpHost() */ public function getUri() { - $qs = $this->getQueryString(); - if (null !== $qs) { + if (null !== $qs = $this->getQueryString()) { $qs = '?'.$qs; } diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php index 05e8717fc0f73..b88350c20fa32 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterProxyListener.php @@ -55,7 +55,7 @@ public function onKernelRequest(GetResponseEvent $event) parse_str($request->query->get('path', ''), $attributes); $request->attributes->add($attributes); - $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params'), $attributes)); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); $request->query->remove('path'); } @@ -76,7 +76,8 @@ protected function validateRequest(Request $request) } // is the Request signed? - if ($this->signer->check($request->getUri())) { + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { return; } diff --git a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php index 408dffc991cd8..4849b8f6b5ec4 100644 --- a/src/Symfony/Component/HttpKernel/HttpContentRenderer.php +++ b/src/Symfony/Component/HttpKernel/HttpContentRenderer.php @@ -90,6 +90,8 @@ public function onKernelResponse(FilterResponseEvent $event) * @param array $options An array of options * * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the strategy does not exist */ public function render($uri, $strategy = 'default', array $options = array()) { diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php index 5198c01ba9729..c0820b8504b3d 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/DefaultRenderingStrategy.php @@ -36,6 +36,10 @@ public function __construct(HttpKernelInterface $kernel) /** * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error */ public function render($uri, Request $request = null, array $options = array()) { diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php index cf9f5720796aa..f77669f484c62 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/EsiRenderingStrategy.php @@ -56,7 +56,7 @@ public function __construct(Esi $esi, RenderingStrategyInterface $defaultStrateg */ public function render($uri, Request $request = null, array $options = array()) { - if (!$this->esi->hasSurrogateEsiCapability($request)) { + if (null === $request || !$this->esi->hasSurrogateEsiCapability($request)) { return $this->defaultStrategy->render($uri, $request, $options); } @@ -69,7 +69,7 @@ public function render($uri, Request $request = null, array $options = array()) $alt = $this->generateProxyUri($alt, $request); } - return $this->esi->renderIncludeTag($uri, $alt, $options['ignore_errors'], isset($options['comment']) ? $options['comment'] : ''); + return $this->esi->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); } /** diff --git a/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php index 408d682326811..a5ba272f81e9b 100644 --- a/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php +++ b/src/Symfony/Component/HttpKernel/RenderingStrategy/GeneratorAwareRenderingStrategy.php @@ -46,6 +46,9 @@ public function setUrlGenerator(UrlGeneratorInterface $generator) * @param Request $request A Request instance * * @return string A proxy URI + * + * @throws \LogicException when the _proxy route is not available + * @throws \LogicException when there is no registered route generator */ protected function generateProxyUri(ControllerReference $reference, Request $request = null) { @@ -63,7 +66,7 @@ protected function generateProxyUri(ControllerReference $reference, Request $req } try { - $uri = $this->generator->generate('_proxy', array('_controller' => $reference->controller, '_format' => $format), true); + $uri = $this->generator->generate('_proxy', array('_controller' => $reference->controller, '_format' => $format), UrlGeneratorInterface::ABSOLUTE_URL); } catch (RouteNotFoundException $e) { throw new \LogicException('Unable to generate a proxy URL as the "_proxy" route is not registered.', 0, $e); } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterProxyListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterProxyListenerTest.php new file mode 100644 index 0000000000000..32b750f9ce670 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterProxyListenerTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\RouterProxyListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\UriSigner; + +class RouterProxyListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + } + + public function testOnlyTrigerredOnProxyRoute() + { + $request = Request::create('http://example.com/foo?path=foo%3D=bar'); + + $listener = new RouterProxyListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, 'foobar'); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + $this->assertTrue($request->query->has('path')); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonSafeMethods() + { + $request = Request::create('http://example.com/foo', 'POST'); + + $listener = new RouterProxyListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonLocalIps() + { + $request = Request::create('http://example.com/foo', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new RouterProxyListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithWrongSignature() + { + $request = Request::create('http://example.com/foo', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new RouterProxyListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + public function testWithSignatureAndNoPath() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/foo'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new RouterProxyListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'foo'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('path')); + } + + public function testWithSignatureAndPath() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/foo?path=bar%3Dbar'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new RouterProxyListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('path')); + } + + private function createGetResponseEvent(Request $request, $route = '_proxy') + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request->attributes->set('_route', $route); + $request->attributes->set('_route_params', array('foo' => 'foo')); + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpContentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpContentRendererTest.php new file mode 100644 index 0000000000000..ff0d4cbdb0776 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/HttpContentRendererTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\HttpContentRenderer; + +class HttpContentRendererTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWhenStrategyDoesNotExist() + { + $renderer = new HttpContentRenderer(); + $renderer->render('/', 'foo'); + } + + public function testRender() + { + $strategy = $this->getMock('Symfony\Component\HttpKernel\RenderingStrategy\RenderingStrategyInterface'); + $strategy + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')) + ; + $strategy + ->expects($this->any()) + ->method('render') + ->with('/', null, array('foo' => 'foo', 'ignore_errors' => true)) + ->will($this->returnValue('foo')) + ; + + $renderer = new HttpContentRenderer(); + $renderer->addStrategy($strategy); + + $this->assertEquals('foo', $renderer->render('/', 'foo', array('foo' => 'foo'))); + } + + /** + * @dataProvider getFixOptionsData + */ + public function testFixOptions($expected, $options) + { + $renderer = new HttpContentRenderer(); + + set_error_handler(function ($errorNumber, $message, $file, $line, $context) { return $errorNumber & E_USER_DEPRECATED; }); + $this->assertEquals($expected, $renderer->fixOptions($options)); + restore_error_handler(); + } + + public function getFixOptionsData() + { + return array( + array(array('strategy' => 'esi'), array('standalone' => true)), + array(array('strategy' => 'esi'), array('standalone' => 'esi')), + array(array('strategy' => 'hinclude'), array('standalone' => 'js')), + ); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/AbstractRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/AbstractRenderingStrategyTest.php new file mode 100644 index 0000000000000..ae3a07f2cce10 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/AbstractRenderingStrategyTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; + +abstract class AbstractRenderingStrategyTest extends \PHPUnit_Framework_TestCase +{ + protected function getUrlGenerator() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $generator + ->expects($this->any()) + ->method('generate') + ->will($this->returnCallback(function ($name, $parameters, $referenceType) { + return '/'.$parameters['_controller'].'.'.$parameters['_format']; + })) + ; + + return $generator; + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php index 48a545147092e..3c55c5905ce22 100644 --- a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/DefaultRenderingStrategyTest.php @@ -9,16 +9,16 @@ * file that was distributed with this source code. */ - namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; +namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; -use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\RenderingStrategy\DefaultRenderingStrategy; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\EventDispatcher\EventDispatcher; -class DefaultRenderingStrategyTest extends \PHPUnit_Framework_TestCase +class DefaultRenderingStrategyTest extends AbstractRenderingStrategyTest { protected function setUp() { @@ -31,6 +31,60 @@ protected function setUp() } } + public function testRender() + { + $strategy = new DefaultRenderingStrategy($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render('/')); + } + + public function testRenderWithControllerReference() + { + $strategy = new DefaultRenderingStrategy($this->getKernel($this->returnValue(new Response('foo')))); + $strategy->setUrlGenerator($this->getUrlGenerator()); + + $this->assertEquals('foo', $strategy->render(new ControllerReference('main_controller', array(), array()))); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $strategy = new DefaultRenderingStrategy($this->getKernel($this->throwException(new \RuntimeException('foo')))); + + $this->assertEquals('foo', $strategy->render('/')); + } + + public function testRenderExceptionIgnoreErrors() + { + $strategy = new DefaultRenderingStrategy($this->getKernel($this->throwException(new \RuntimeException('foo')))); + + $this->assertNull($strategy->render('/', null, array('ignore_errors' => true))); + } + + public function testRenderExceptionIgnoreErrorsWithAlt() + { + $strategy = new DefaultRenderingStrategy($this->getKernel($this->onConsecutiveCalls( + $this->throwException(new \RuntimeException('foo')), + $this->returnValue(new Response('bar')) + ))); + + $this->assertEquals('bar', $strategy->render('/', null, array('ignore_errors' => true, 'alt' => '/foo'))); + } + + private function getKernel($returnValue) + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() { $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/EsiRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/EsiRenderingStrategyTest.php new file mode 100644 index 0000000000000..513e30039c037 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/EsiRenderingStrategyTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\RenderingStrategy\EsiRenderingStrategy; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; + +class EsiRenderingStrategyTest extends AbstractRenderingStrategyTest +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + } + + public function testRenderFallbackToDefaultStrategyIfNoRequest() + { + $strategy = new EsiRenderingStrategy(new Esi(), $this->getDefaultStrategy(true)); + $strategy->render('/'); + } + + public function testRenderFallbackToDefaultStrategyIfEsiNotSupported() + { + $strategy = new EsiRenderingStrategy(new Esi(), $this->getDefaultStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRender() + { + $strategy = new EsiRenderingStrategy(new Esi(), $this->getDefaultStrategy()); + $strategy->setUrlGenerator($this->getUrlGenerator()); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)); + $this->assertEquals("\n", $strategy->render('/', $request, array('comment' => 'This is a comment'))); + $this->assertEquals('', $strategy->render('/', $request, array('alt' => 'foo'))); + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()), $request, array('alt' => new ControllerReference('alt_controller', array(), array())))); + } + + private function getDefaultStrategy($called = false) + { + $default = $this->getMockBuilder('Symfony\Component\HttpKernel\RenderingStrategy\DefaultRenderingStrategy')->disableOriginalConstructor()->getMock(); + + if ($called) { + $default->expects($this->once())->method('render'); + } + + return $default; + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/GeneratorAwareRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/GeneratorAwareRenderingStrategyTest.php new file mode 100644 index 0000000000000..387ab3e2a0f20 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/GeneratorAwareRenderingStrategyTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\RenderingStrategy\GeneratorAwareRenderingStrategy; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; + +class GeneratorAwareRenderingStrategyTest extends AbstractRenderingStrategyTest +{ + protected function setUp() + { + if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + } + + /** + * @expectedException \LogicException + */ + public function testGenerateProxyUriWithNoGenerator() + { + $strategy = new Strategy(); + $strategy->doGenerateProxyUri(new ControllerReference('controller', array(), array())); + } + + /** + * @expectedException \LogicException + */ + public function testGenerateProxyUriWhenRouteNotFound() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $generator + ->expects($this->once()) + ->method('generate') + ->will($this->throwException(new RouteNotFoundException())) + ; + + $strategy = new Strategy(); + $strategy->setUrlGenerator($generator); + $strategy->doGenerateProxyUri(new ControllerReference('controller', array(), array())); + } + + /** + * @dataProvider getGeneratorProxyUriData + */ + public function testGenerateProxyUri($uri, $controller) + { + $this->assertEquals($uri, $this->getStrategy()->doGenerateProxyUri($controller)); + } + + public function getGeneratorProxyUriData() + { + return array( + array('/controller.html', new ControllerReference('controller', array(), array())), + array('/controller.xml', new ControllerReference('controller', array('_format' => 'xml'), array())), + array('/controller.json?path=foo%3Dfoo', new ControllerReference('controller', array('foo' => 'foo', '_format' => 'json'), array())), + array('/controller.html?bar=bar&path=foo%3Dfoo', new ControllerReference('controller', array('foo' => 'foo'), array('bar' => 'bar'))), + array('/controller.html?foo=foo', new ControllerReference('controller', array(), array('foo' => 'foo'))), + ); + } + + public function testGenerateProxyUriWithARequest() + { + $request = Request::create('/'); + $request->attributes->set('_format', 'json'); + $controller = new ControllerReference('controller', array(), array()); + + $this->assertEquals('/controller.json', $this->getStrategy()->doGenerateProxyUri($controller, $request)); + } + + private function getStrategy() + { + $strategy = new Strategy(); + $strategy->setUrlGenerator($this->getUrlGenerator()); + + return $strategy; + } +} + +class Strategy extends GeneratorAwareRenderingStrategy +{ + public function render($uri, Request $request = null, array $options = array()) {} + public function getName() {} + + public function doGenerateProxyUri(ControllerReference $reference, Request $request = null) + { + return parent::generateProxyUri($reference, $request); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/HIncludeRenderingStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/HIncludeRenderingStrategyTest.php new file mode 100644 index 0000000000000..ecc99665f8c70 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/RenderingStrategy/HIncludeRenderingStrategyTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\RenderingStrategy; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\RenderingStrategy\HIncludeRenderingStrategy; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpFoundation\Request; + +class HIncludeRenderingStrategyTest extends AbstractRenderingStrategyTest +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + } + + /** + * @expectedException \LogicException + */ + public function testRenderExceptionWhenControllerAndNoSigner() + { + $strategy = new HIncludeRenderingStrategy(); + $strategy->render(new ControllerReference('main_controller', array(), array())); + } + + public function testRenderWithControllerAndSigner() + { + $strategy = new HIncludeRenderingStrategy(null, new UriSigner('foo')); + $strategy->setUrlGenerator($this->getUrlGenerator()); + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()))); + } + + public function testRenderWithUri() + { + $strategy = new HIncludeRenderingStrategy(); + $this->assertEquals('', $strategy->render('/foo')); + + $strategy = new HIncludeRenderingStrategy(null, new UriSigner('foo')); + $this->assertEquals('', $strategy->render('/foo')); + } + + public function testRenderWhithDefault() + { + // only default + $strategy = new HIncludeRenderingStrategy(); + $this->assertEquals('default', $strategy->render('/foo', null, array('default' => 'default'))); + + // only global default + $strategy = new HIncludeRenderingStrategy(null, null, 'global_default'); + $this->assertEquals('global_default', $strategy->render('/foo', null, array())); + + // global default and default + $strategy = new HIncludeRenderingStrategy(null, null, 'global_default'); + $this->assertEquals('default', $strategy->render('/foo', null, array('default' => 'default'))); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php b/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php new file mode 100644 index 0000000000000..8ffc2bfbbd872 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends \PHPUnit_Framework_TestCase +{ + public function testSign() + { + $signer = new UriSigner('foobar'); + + $this->assertContains('?_hash=', $signer->sign('http://example.com/foo')); + $this->assertContains('&_hash=', $signer->sign('http://example.com/foo?foo=bar')); + } + + public function testCheck() + { + $signer = new UriSigner('foobar'); + + $this->assertFalse($signer->check('http://example.com/foo?_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo')); + + $this->assertTrue($signer->check($signer->sign('http://example.com/foo'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar'))); + } +} diff --git a/src/Symfony/Component/HttpKernel/UriSigner.php b/src/Symfony/Component/HttpKernel/UriSigner.php index 3530c31a77e7c..45825fe246052 100644 --- a/src/Symfony/Component/HttpKernel/UriSigner.php +++ b/src/Symfony/Component/HttpKernel/UriSigner.php @@ -48,19 +48,22 @@ public function sign($uri) /** * Checks that a URI contains the correct hash. * + * The _hash query string parameter must be the last one + * (as it is generated that way by the sign() method, it should + * never be a problem). + * * @param string $uri A signed URI * * @return Boolean True if the URI is signed correctly, false otherwise */ public function check($uri) { - if (!preg_match('/(\?|&)_hash=(.+?)(&|$)/', $uri, $matches, PREG_OFFSET_CAPTURE)) { + if (!preg_match('/(\?|&)_hash=(.+?)$/', $uri, $matches, PREG_OFFSET_CAPTURE)) { return false; } // the naked URI is the URI without the _hash parameter (we need to keep the ? if there is some other parameters after) - $offset = ('?' == $matches[1][0] && '&' != $matches[3][0]) ? 0 : 1; - $nakedUri = substr($uri, 0, $matches[0][1] + $offset).substr($uri, $matches[0][1] + strlen($matches[0][0])); + $nakedUri = substr($uri, 0, $matches[0][1]).substr($uri, $matches[0][1] + strlen($matches[0][0])); return $this->computeHash($nakedUri) === $matches[2][0]; } From 76fefe35375abc7b29ae1aa340a20138d863553f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 10 Jan 2013 16:47:11 +0100 Subject: [PATCH 13/13] updated CHANGELOG and UPGRADE files --- UPGRADE-2.2.md | 11 +++++++---- src/Symfony/Bridge/Twig/CHANGELOG.md | 6 ++++-- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 13 +++++++++---- src/Symfony/Component/HttpFoundation/CHANGELOG.md | 2 ++ src/Symfony/Component/HttpFoundation/Request.php | 2 ++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 5 +++++ 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index c485bf6112fa8..3b8583d908af5 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -14,11 +14,9 @@ After: ``` - {% render url('post_list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error' } %} + {% render controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error' } %} ``` - where `post_list` is the route name for the `BlogBundle:Post:list` controller. - ### HttpFoundation * The MongoDbSessionHandler default field names and timestamp type have changed. @@ -409,7 +407,12 @@ render($view['router']->generate('post_list', array('limit' => 2)), array('alt' => 'BlogBundle:Post:error')) ?> ``` - where `post_list` is the route name for the `BlogBundle:Post:list` controller. + where `post_list` is the route name for the `BlogBundle:Post:list` + controller, or if you don't want to create a route: + + ``` + render(new ControllerReference('BlogBundle:Post:list', array('limit' => 2)), array('alt' => 'BlogBundle:Post:error')) ?> + ``` #### Configuration diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 420a99b61ff8b..343c7743a3ca2 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -4,8 +4,10 @@ CHANGELOG 2.2.0 ----- - * [BC BREAK] restricted the `render` tag to only accept URIs as reference (the signature changed) - * added a render function to render a request + * added a `controller` function to help generating controller references + * added a `render_esi` and a `render_hinclude` function + * [BC BREAK] restricted the `render` tag to only accept URIs or ControllerReference instances (the signature changed) + * added a `render` function to render a request * The `app` global variable is now injected even when using the twig service directly. * Added an optional parameter to the `path` and `url` function which allows to generate relative paths (e.g. "../parent-file") and scheme-relative URLs (e.g. "//example.com/dir/file"). diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index af67c70ea27e5..5797f3f19f892 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,11 +4,16 @@ CHANGELOG 2.2.0 ----- - * [BC BREAK] restricted the `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method to only accept URIs as reference + * added a new `uri_signer` service to help sign URIs + * deprecated `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` and `Symfony\Bundle\FrameworkBundle\HttpKernel::forward()` + * deprecated the `Symfony\Bundle\FrameworkBundle\HttpKernel` class in favor of `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * added support for adding new HTTP content rendering strategies (like ESI and Hinclude) + in the DIC via the `kernel.content_renderer_strategy` tag + * [BC BREAK] restricted the `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method to only accept URIs or ControllerReference instances * `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method signature changed and the first argument - must now be a URI (the `generateInternalUri()` method was removed) - * The internal routes have been removed (`Resources/config/routing/internal.xml`) - * The `render` method of the `actions` templating helper signature and arguments changed: + must now be a URI or a ControllerReference instance (the `generateInternalUri()` method was removed) + * The internal routes (`Resources/config/routing/internal.xml`) have been replaced with a new proxy route (`Resources/config/routing/proxy.xml`) + * The `render` method of the `actions` templating helper signature and arguments changed * replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver * replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher * added Client::enableProfiler() diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index c83695f7478de..1ec5492883e53 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 2.2.0 ----- + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() * added a IpUtils class to check if an IP belongs to a CIDR * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to enable it) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 0bac66ada759e..96ade68f01d32 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -527,6 +527,8 @@ public static function setTrustedHeaderName($key, $value) * false otherwise. * * @return boolean + * + * @deprecated Deprecated since version 2.2, to be removed in 2.3. Use getTrustedProxies instead. */ public static function isProxyTrusted() { diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 75b5d9321a7fc..15ddbf199d791 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,11 @@ CHANGELOG 2.2.0 ----- + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\HttpContentRenderer and rendering strategies (in Symfony\Component\HttpKernel\RenderingStrategy) + * added Symfony\Component\HttpKernel\EventListener\RouterProxyListener + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the HttpContentRenderer class) * [BC BREAK] renamed TimeDataCollector::getTotalTime() to TimeDataCollector::getDuration() * updated the MemoryDataCollector to include the memory used in the