diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index c9a4482b0b..ec6c1f0a28 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -81,6 +81,7 @@ class Configuration implements ConfigurationInterface
$this->addCsrfSection($rootNode);
$this->addFormSection($rootNode);
$this->addEsiSection($rootNode);
+ $this->addSsiSection($rootNode);
$this->addFragmentsSection($rootNode);
$this->addProfilerSection($rootNode);
$this->addRouterSection($rootNode);
@@ -148,6 +149,17 @@ class Configuration implements ConfigurationInterface
;
}
+ private function addSsiSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->arrayNode('ssi')
+ ->info('ssi configuration')
+ ->canBeEnabled()
+ ->end()
+ ->end();
+ }
+
private function addFragmentsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index f9146c242e..49121e716e 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -122,6 +122,7 @@ class FrameworkExtension extends Extension
$this->registerValidationConfiguration($config['validation'], $container, $loader);
$this->registerEsiConfiguration($config['esi'], $container, $loader);
+ $this->registerSsiConfiguration($config['ssi'], $container, $loader);
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
$this->registerTranslatorConfiguration($config['translator'], $container);
@@ -208,6 +209,22 @@ class FrameworkExtension extends Extension
$loader->load('esi.xml');
}
+ /**
+ * Loads the SSI configuration.
+ *
+ * @param array $config An SSI configuration array
+ * @param ContainerBuilder $container A ContainerBuilder instance
+ * @param XmlFileLoader $loader An XmlFileLoader instance
+ */
+ private function registerSsiConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
+ {
+ if (!$this->isConfigEnabled($container, $config)) {
+ return;
+ }
+
+ $loader->load('ssi.xml');
+ }
+
/**
* Loads the fragments configuration.
*
diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php
index 2e5a2312dd..bc5a0cc82f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php
+++ b/src/Symfony/Bundle/FrameworkBundle/HttpCache/HttpCache.php
@@ -39,7 +39,7 @@ abstract class HttpCache extends BaseHttpCache
$this->kernel = $kernel;
$this->cacheDir = $cacheDir;
- parent::__construct($kernel, $this->createStore(), $this->createEsi(), array_merge(array('debug' => $kernel->isDebug()), $this->getOptions()));
+ parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge(array('debug' => $kernel->isDebug()), $this->getOptions()));
}
/**
@@ -55,7 +55,7 @@ abstract class HttpCache extends BaseHttpCache
{
$this->getKernel()->boot();
$this->getKernel()->getContainer()->set('cache', $this);
- $this->getKernel()->getContainer()->set('esi', $this->getEsi());
+ $this->getKernel()->getContainer()->set($this->getSurrogate()->getName(), $this->getSurrogate());
return parent::forward($request, $raw, $entry);
}
@@ -70,6 +70,18 @@ abstract class HttpCache extends BaseHttpCache
return array();
}
+ protected function createSurrogate()
+ {
+ return $this->createEsi();
+ }
+
+ /**
+ * Creates new ESI instance
+ *
+ * @return Esi
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use createSurrogate() instead
+ */
protected function createEsi()
{
return new Esi();
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml
index a1beee30a1..06003d684f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml
@@ -10,6 +10,7 @@
Symfony\Bundle\FrameworkBundle\Fragment\ContainerAwareHIncludeFragmentRenderer
Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer
+ Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer
/_fragment
@@ -41,5 +42,17 @@
%fragment.path%
+
+
+
+
+
+
+ %fragment.path%
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/ssi.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/ssi.xml
new file mode 100644
index 0000000000..fd2fdd776e
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/ssi.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Symfony\Component\HttpKernel\HttpCache\Ssi
+ Symfony\Component\HttpKernel\EventListener\SurrogateListener
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index 794ca1922c..05f74ad4c8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -105,6 +105,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'field_name' => '_token',
),
'esi' => array('enabled' => false),
+ 'ssi' => array('enabled' => false),
'fragments' => array(
'enabled' => false,
'path' => '/_fragment',
diff --git a/src/Symfony/Component/HttpKernel/EventListener/EsiListener.php b/src/Symfony/Component/HttpKernel/EventListener/EsiListener.php
index 686778afc4..6380169017 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/EsiListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/EsiListener.php
@@ -11,48 +11,13 @@
namespace Symfony\Component\HttpKernel\EventListener;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
-use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\HttpKernel\HttpCache\Esi;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
/**
* EsiListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for ESI.
*
* @author Fabien Potencier
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use SurrogateListener instead
*/
-class EsiListener implements EventSubscriberInterface
+class EsiListener extends SurrogateListener
{
- private $esi;
-
- /**
- * Constructor.
- *
- * @param Esi $esi An ESI instance
- */
- public function __construct(Esi $esi = null)
- {
- $this->esi = $esi;
- }
-
- /**
- * Filters the Response.
- *
- * @param FilterResponseEvent $event A FilterResponseEvent instance
- */
- public function onKernelResponse(FilterResponseEvent $event)
- {
- if (!$event->isMasterRequest() || null === $this->esi) {
- return;
- }
-
- $this->esi->addSurrogateControl($event->getResponse());
- }
-
- public static function getSubscribedEvents()
- {
- return array(
- KernelEvents::RESPONSE => 'onKernelResponse',
- );
- }
}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php
new file mode 100644
index 0000000000..00f4fbf24e
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/EventListener/SurrogateListener.php
@@ -0,0 +1,58 @@
+
+ *
+ * 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\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates
+ *
+ * @author Fabien Potencier
+ */
+class SurrogateListener implements EventSubscriberInterface
+{
+ private $surrogate;
+
+ /**
+ * Constructor.
+ *
+ * @param SurrogateInterface $surrogate An SurrogateInterface instance
+ */
+ public function __construct(SurrogateInterface $surrogate = null)
+ {
+ $this->surrogate = $surrogate;
+ }
+
+ /**
+ * Filters the Response.
+ *
+ * @param FilterResponseEvent $event A FilterResponseEvent instance
+ */
+ public function onKernelResponse(FilterResponseEvent $event)
+ {
+ if (!$event->isMasterRequest() || null === $this->surrogate) {
+ return;
+ }
+
+ $this->surrogate->addSurrogateControl($event->getResponse());
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ KernelEvents::RESPONSE => 'onKernelResponse',
+ );
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php
new file mode 100644
index 0000000000..69e43a371f
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Fragment;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Controller\ControllerReference;
+use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
+
+/**
+ * Implements Surrogate rendering strategy.
+ *
+ * @author Fabien Potencier
+ */
+abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer
+{
+ private $surrogate;
+ private $inlineStrategy;
+
+ /**
+ * Constructor.
+ *
+ * The "fallback" strategy when surrogate is not available should always be an
+ * instance of InlineFragmentRenderer.
+ *
+ * @param SurrogateInterface $surrogate An Surrogate instance
+ * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported
+ */
+ public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy)
+ {
+ $this->surrogate = $surrogate;
+ $this->inlineStrategy = $inlineStrategy;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Note that if the current Request has no surrogate capability, this method
+ * falls back to use the inline rendering strategy.
+ *
+ * Additional available options:
+ *
+ * * alt: an alternative URI to render in case of an error
+ * * comment: a comment to add when returning the surrogate tag
+ *
+ * Note, that not all surrogate strategies support all options. For now
+ * 'alt' and 'comment' are only supported by ESI.
+ *
+ * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface
+ */
+ public function render($uri, Request $request, array $options = array())
+ {
+ if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) {
+ return $this->inlineStrategy->render($uri, $request, $options);
+ }
+
+ if ($uri instanceof ControllerReference) {
+ $uri = $this->generateFragmentUri($uri, $request);
+ }
+
+ $alt = isset($options['alt']) ? $options['alt'] : null;
+ if ($alt instanceof ControllerReference) {
+ $alt = $this->generateFragmentUri($alt, $request);
+ }
+
+ $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
+
+ return new Response($tag);
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php
index 620c71a878..a4570e3beb 100644
--- a/src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php
+++ b/src/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php
@@ -11,69 +11,13 @@
namespace Symfony\Component\HttpKernel\Fragment;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\Controller\ControllerReference;
-use Symfony\Component\HttpKernel\HttpCache\Esi;
-
/**
* Implements the ESI rendering strategy.
*
* @author Fabien Potencier
*/
-class EsiFragmentRenderer extends RoutableFragmentRenderer
+class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
{
- private $esi;
- private $inlineStrategy;
-
- /**
- * Constructor.
- *
- * The "fallback" strategy when ESI is not available should always be an
- * instance of InlineFragmentRenderer.
- *
- * @param Esi $esi An Esi instance
- * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when ESI is not supported
- */
- public function __construct(Esi $esi = null, InlineFragmentRenderer $inlineStrategy)
- {
- $this->esi = $esi;
- $this->inlineStrategy = $inlineStrategy;
- }
-
- /**
- * {@inheritdoc}
- *
- * Note that if the current Request has no ESI capability, this method
- * falls back to use the inline rendering strategy.
- *
- * Additional available options:
- *
- * * 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, array $options = array())
- {
- if (!$this->esi || !$this->esi->hasSurrogateEsiCapability($request)) {
- return $this->inlineStrategy->render($uri, $request, $options);
- }
-
- if ($uri instanceof ControllerReference) {
- $uri = $this->generateFragmentUri($uri, $request);
- }
-
- $alt = isset($options['alt']) ? $options['alt'] : null;
- if ($alt instanceof ControllerReference) {
- $alt = $this->generateFragmentUri($alt, $request);
- }
-
- $tag = $this->esi->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : '');
-
- return new Response($tag);
- }
-
/**
* {@inheritdoc}
*/
diff --git a/src/Symfony/Component/HttpKernel/Fragment/SsiFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/SsiFragmentRenderer.php
new file mode 100644
index 0000000000..1d524391b9
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Fragment/SsiFragmentRenderer.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Fragment;
+
+use Symfony\Component\HttpKernel\Controller\ControllerReference;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\UriSigner;
+
+/**
+ * Implements the ESI rendering strategy.
+ *
+ * @author Sebastian Krebs
+ */
+class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer
+{
+ /** @var UriSigner */
+ private $signer;
+
+ /**
+ * Set uri signer
+ *
+ * @param UriSigner $signer
+ */
+ public function setUriSigner(UriSigner $signer)
+ {
+ $this->signer = $signer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'ssi';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true)
+ {
+ $uri = parent::generateFragmentUri($reference, $request, $absolute, $strict);
+
+ if ($this->signer) {
+ $uri = $this->signer->sign($uri);
+ }
+
+ return $uri;
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
index 3ba9ecd818..70d8e11773 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
@@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
*
* @author Fabien Potencier
*/
-class Esi
+class Esi implements SurrogateInterface
{
private $contentTypes;
@@ -41,10 +41,15 @@ class Esi
$this->contentTypes = $contentTypes;
}
+ public function getName()
+ {
+ return 'esi';
+ }
+
/**
* Returns a new cache strategy instance.
*
- * @return EsiResponseCacheStrategyInterface A EsiResponseCacheStrategyInterface instance
+ * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance
*/
public function createCacheStrategy()
{
@@ -58,6 +63,20 @@ class Esi
*
* @return bool true if one surrogate has ESI/1.0 capability, false otherwise
*/
+ public function hasSurrogateCapability(Request $request)
+ {
+ return $this->hasSurrogateEsiCapability($request);
+ }
+
+ /**
+ * Checks that at least one surrogate has ESI/1.0 capability.
+ *
+ * @param Request $request A Request instance
+ *
+ * @return bool true if one surrogate has ESI/1.0 capability, false otherwise
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use hasSurrogateCapability() instead
+ */
public function hasSurrogateEsiCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
@@ -72,6 +91,18 @@ class Esi
*
* @param Request $request A Request instance
*/
+ public function addSurrogateCapability(Request $request)
+ {
+ $this->addSurrogateEsiCapability($request);
+ }
+
+ /**
+ * Adds ESI/1.0 capability to the given Request.
+ *
+ * @param Request $request A Request instance
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use addSurrogateCapability() instead
+ */
public function addSurrogateEsiCapability(Request $request)
{
$current = $request->headers->get('Surrogate-Capability');
@@ -101,6 +132,20 @@ class Esi
*
* @return bool true if the Response needs to be parsed, false otherwise
*/
+ public function needsParsing(Response $response)
+ {
+ return $this->needsEsiParsing($response);
+ }
+
+ /**
+ * Checks that the Response needs to be parsed for ESI tags.
+ *
+ * @param Response $response A Response instance
+ *
+ * @return bool true if the Response needs to be parsed, false otherwise
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use needsParsing() instead
+ */
public function needsEsiParsing(Response $response)
{
if (!$control = $response->headers->get('Surrogate-Control')) {
@@ -236,7 +281,7 @@ class Esi
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
- return sprintf('esi->handle($this, \'%s\', \'%s\', %s) ?>'."\n",
+ return sprintf('surrogate->handle($this, \'%s\', \'%s\', %s) ?>'."\n",
$options['src'],
isset($options['alt']) ? $options['alt'] : null,
isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false'
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php
index 6384af9660..1bef147595 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php
@@ -15,8 +15,6 @@
namespace Symfony\Component\HttpKernel\HttpCache;
-use Symfony\Component\HttpFoundation\Response;
-
/**
* EsiResponseCacheStrategy knows how to compute the Response cache HTTP header
* based on the different ESI response cache headers.
@@ -25,61 +23,9 @@ use Symfony\Component\HttpFoundation\Response;
* or force validation if one of the ESI has validation cache strategy.
*
* @author Fabien Potencier
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategy instead
*/
-class EsiResponseCacheStrategy implements EsiResponseCacheStrategyInterface
+class EsiResponseCacheStrategy extends ResponseCacheStrategy implements EsiResponseCacheStrategyInterface
{
- private $cacheable = true;
- private $embeddedResponses = 0;
- private $ttls = array();
- private $maxAges = array();
-
- /**
- * {@inheritdoc}
- */
- public function add(Response $response)
- {
- if ($response->isValidateable()) {
- $this->cacheable = false;
- } else {
- $this->ttls[] = $response->getTtl();
- $this->maxAges[] = $response->getMaxAge();
- }
-
- $this->embeddedResponses++;
- }
-
- /**
- * {@inheritdoc}
- */
- public function update(Response $response)
- {
- // if we have no embedded Response, do nothing
- if (0 === $this->embeddedResponses) {
- return;
- }
-
- // Remove validation related headers in order to avoid browsers using
- // their own cache, because some of the response content comes from
- // at least one embedded response (which likely has a different caching strategy).
- if ($response->isValidateable()) {
- $response->setEtag(null);
- $response->setLastModified(null);
- $this->cacheable = false;
- }
-
- if (!$this->cacheable) {
- $response->headers->set('Cache-Control', 'no-cache, must-revalidate');
-
- return;
- }
-
- $this->ttls[] = $response->getTtl();
- $this->maxAges[] = $response->getMaxAge();
-
- if (null !== $maxAge = min($this->maxAges)) {
- $response->setSharedMaxAge($maxAge);
- $response->headers->set('Age', $maxAge - min($this->ttls));
- }
- $response->setMaxAge(0);
- }
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php
index 0fb8a12436..03df0575a5 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php
@@ -15,27 +15,14 @@
namespace Symfony\Component\HttpKernel\HttpCache;
-use Symfony\Component\HttpFoundation\Response;
-
/**
- * EsiResponseCacheStrategyInterface implementations know how to compute the
- * Response cache HTTP header based on the different ESI response cache headers.
+ * ResponseCacheStrategyInterface implementations know how to compute the
+ * Response cache HTTP header based on the different response cache headers.
*
* @author Fabien Potencier
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategyInterface instead
*/
-interface EsiResponseCacheStrategyInterface
+interface EsiResponseCacheStrategyInterface extends ResponseCacheStrategyInterface
{
- /**
- * Adds a Response.
- *
- * @param Response $response
- */
- public function add(Response $response);
-
- /**
- * Updates the Response HTTP headers based on the embedded Responses.
- *
- * @param Response $response
- */
- public function update(Response $response);
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
index 84a10f7d49..3c5eb065d1 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php
@@ -32,8 +32,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
private $kernel;
private $store;
private $request;
- private $esi;
- private $esiCacheStrategy;
+ private $surrogate;
+ private $surrogateCacheStrategy;
private $options = array();
private $traces = array();
@@ -72,16 +72,16 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
* This setting is overridden by the stale-if-error HTTP Cache-Control extension
* (see RFC 5861).
*
- * @param HttpKernelInterface $kernel An HttpKernelInterface instance
- * @param StoreInterface $store A Store instance
- * @param Esi $esi An Esi instance
- * @param array $options An array of options
+ * @param HttpKernelInterface $kernel An HttpKernelInterface instance
+ * @param StoreInterface $store A Store instance
+ * @param SurrogateInterface $surrogate A SurrogateInterface instance
+ * @param array $options An array of options
*/
- public function __construct(HttpKernelInterface $kernel, StoreInterface $store, Esi $esi = null, array $options = array())
+ public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array())
{
$this->store = $store;
$this->kernel = $kernel;
- $this->esi = $esi;
+ $this->surrogate = $surrogate;
// needed in case there is a fatal error because the backend is too slow to respond
register_shutdown_function(array($this->store, 'cleanup'));
@@ -153,14 +153,33 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
}
+ /**
+ * Gets the Surrogate instance
+ *
+ * @throws \LogicException
+ * @return SurrogateInterface A Surrogate instance
+ */
+ public function getSurrogate()
+ {
+ return $this->getEsi();
+ }
+
+
/**
* Gets the Esi instance
*
+ * @throws \LogicException
* @return Esi An Esi instance
+ *
+ * @deprecated Deprecated since version 2.6, to be removed in 3.0. Use getSurrogate() instead
*/
public function getEsi()
{
- return $this->esi;
+ if (!$this->surrogate instanceof Esi) {
+ throw new \LogicException('This instance of HttpCache was not set up to use ESI as surrogate handler. You must overwrite and use createSurrogate');
+ }
+
+ return $this->surrogate;
}
/**
@@ -174,8 +193,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->traces = array();
$this->request = $request;
- if (null !== $this->esi) {
- $this->esiCacheStrategy = $this->esi->createCacheStrategy();
+ if (null !== $this->surrogate) {
+ $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy();
}
}
@@ -201,11 +220,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
$response->headers->set('X-Symfony-Cache', $this->getLog());
}
- if (null !== $this->esi) {
+ if (null !== $this->surrogate) {
if (HttpKernelInterface::MASTER_REQUEST === $type) {
- $this->esiCacheStrategy->update($response);
+ $this->surrogateCacheStrategy->update($response);
} else {
- $this->esiCacheStrategy->add($response);
+ $this->surrogateCacheStrategy->add($response);
}
}
@@ -446,8 +465,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
*/
protected function forward(Request $request, $catch = false, Response $entry = null)
{
- if ($this->esi) {
- $this->esi->addSurrogateEsiCapability($request);
+ if ($this->surrogate) {
+ $this->surrogate->addSurrogateCapability($request);
}
// modify the X-Forwarded-For header if needed
@@ -640,8 +659,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
protected function processResponseBody(Request $request, Response $response)
{
- if (null !== $this->esi && $this->esi->needsEsiParsing($response)) {
- $this->esi->process($request, $response);
+ if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) {
+ $this->surrogate->process($request, $response);
}
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
new file mode 100644
index 0000000000..d22dff1e5f
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php
@@ -0,0 +1,85 @@
+
+ *
+ * This code is partially based on the Rack-Cache library by Ryan Tomayko,
+ * which is released under the MIT license.
+ * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801)
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\HttpCache;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * ResponseCacheStrategy knows how to compute the Response cache HTTP header
+ * based on the different response cache headers.
+ *
+ * This implementation changes the master response TTL to the smallest TTL received
+ * or force validation if one of the surrogates has validation cache strategy.
+ *
+ * @author Fabien Potencier
+ */
+class ResponseCacheStrategy implements ResponseCacheStrategyInterface
+{
+ private $cacheable = true;
+ private $embeddedResponses = 0;
+ private $ttls = array();
+ private $maxAges = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function add(Response $response)
+ {
+ if ($response->isValidateable()) {
+ $this->cacheable = false;
+ } else {
+ $this->ttls[] = $response->getTtl();
+ $this->maxAges[] = $response->getMaxAge();
+ }
+
+ $this->embeddedResponses++;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update(Response $response)
+ {
+ // if we have no embedded Response, do nothing
+ if (0 === $this->embeddedResponses) {
+ return;
+ }
+
+ // Remove validation related headers in order to avoid browsers using
+ // their own cache, because some of the response content comes from
+ // at least one embedded response (which likely has a different caching strategy).
+ if ($response->isValidateable()) {
+ $response->setEtag(null);
+ $response->setLastModified(null);
+ $this->cacheable = false;
+ }
+
+ if (!$this->cacheable) {
+ $response->headers->set('Cache-Control', 'no-cache, must-revalidate');
+
+ return;
+ }
+
+ $this->ttls[] = $response->getTtl();
+ $this->maxAges[] = $response->getMaxAge();
+
+ if (null !== $maxAge = min($this->maxAges)) {
+ $response->setSharedMaxAge($maxAge);
+ $response->headers->set('Age', $maxAge - min($this->ttls));
+ }
+ $response->setMaxAge(0);
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php
new file mode 100644
index 0000000000..d70c2e06ec
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategyInterface.php
@@ -0,0 +1,41 @@
+
+ *
+ * This code is partially based on the Rack-Cache library by Ryan Tomayko,
+ * which is released under the MIT license.
+ * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801)
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\HttpCache;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * ResponseCacheStrategyInterface implementations know how to compute the
+ * Response cache HTTP header based on the different response cache headers.
+ *
+ * @author Fabien Potencier
+ */
+interface ResponseCacheStrategyInterface
+{
+ /**
+ * Adds a Response.
+ *
+ * @param Response $response
+ */
+ public function add(Response $response);
+
+ /**
+ * Updates the Response HTTP headers based on the embedded Responses.
+ *
+ * @param Response $response
+ */
+ public function update(Response $response);
+}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php
new file mode 100644
index 0000000000..6f9d102b95
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Ssi.php
@@ -0,0 +1,197 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\HttpCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * Ssi implements the SSI capabilities to Request and Response instances.
+ *
+ * @author Sebastian Krebs
+ */
+class Ssi implements SurrogateInterface
+{
+ private $contentTypes;
+
+ /**
+ * Constructor.
+ *
+ * @param array $contentTypes An array of content-type that should be parsed for SSI information.
+ * (default: text/html, text/xml, application/xhtml+xml, and application/xml)
+ */
+ public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'))
+ {
+ $this->contentTypes = $contentTypes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'ssi';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createCacheStrategy()
+ {
+ return new ResponseCacheStrategy();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasSurrogateCapability(Request $request)
+ {
+ if (null === $value = $request->headers->get('Surrogate-Capability')) {
+ return false;
+ }
+
+ return false !== strpos($value, 'SSI/1.0');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSurrogateCapability(Request $request)
+ {
+ $current = $request->headers->get('Surrogate-Capability');
+ $new = 'symfony2="SSI/1.0"';
+
+ $request->headers->set('Surrogate-Capability', $current ? $current . ', ' . $new : $new);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSurrogateControl(Response $response)
+ {
+ if (false !== strpos($response->getContent(), '', $uri);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(Request $request, Response $response)
+ {
+ $this->request = $request;
+ $type = $response->headers->get('Content-Type');
+ if (empty($type)) {
+ $type = 'text/html';
+ }
+
+ $parts = explode(';', $type);
+ if (!in_array($parts[0], $this->contentTypes)) {
+ return $response;
+ }
+
+ // we don't use a proper XML parser here as we can have SSI tags in a plain text response
+ $content = $response->getContent();
+ $content = str_replace(array('', '<%'), array('', ''), $content);
+ $content = preg_replace_callback('##', array($this, 'handleIncludeTag'), $content);
+
+ $response->setContent($content);
+ $response->headers->set('X-Body-Eval', 'SSI');
+
+ // remove SSI/1.0 from the Surrogate-Control header
+ if ($response->headers->has('Surrogate-Control')) {
+ $value = $response->headers->get('Surrogate-Control');
+ if ('content="SSI/1.0"' == $value) {
+ $response->headers->remove('Surrogate-Control');
+ } elseif (preg_match('#,\s*content="SSI/1.0"#', $value)) {
+ $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="SSI/1.0"#', '', $value));
+ } elseif (preg_match('#content="SSI/1.0",\s*#', $value)) {
+ $response->headers->set('Surrogate-Control', preg_replace('#content="SSI/1.0",\s*#', '', $value));
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors)
+ {
+ $subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all());
+
+ try {
+ $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true);
+
+ if (!$response->isSuccessful()) {
+ throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode()));
+ }
+
+ return $response->getContent();
+ } catch (\Exception $e) {
+ if ($alt) {
+ return $this->handle($cache, $alt, '', $ignoreErrors);
+ }
+
+ if (!$ignoreErrors) {
+ throw $e;
+ }
+ }
+ }
+
+ /**
+ * Handles an SSI include tag (called internally).
+ *
+ * @param array $attributes An array containing the attributes.
+ *
+ * @return string The response content for the include.
+ *
+ * @throws \RuntimeException
+ */
+ private function handleIncludeTag($attributes)
+ {
+ $options = array();
+ preg_match_all('/(virtual)="([^"]*?)"/', $attributes[1], $matches, PREG_SET_ORDER);
+ foreach ($matches as $set) {
+ $options[$set[1]] = $set[2];
+ }
+
+ if (!isset($options['virtual'])) {
+ throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.');
+ }
+
+ return sprintf('surrogate->handle($this, \'%s\', \'%s\', %s) ?>' . "\n",
+ $options['virtual'],
+ '',
+ 'false'
+ );
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php
new file mode 100644
index 0000000000..c26304dfdf
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/HttpCache/SurrogateInterface.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\HttpCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+interface SurrogateInterface
+{
+ /**
+ * Returns surrogate name
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Returns a new cache strategy instance.
+ *
+ * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance
+ */
+ public function createCacheStrategy();
+
+ /**
+ * Checks that at least one surrogate has Surrogate capability.
+ *
+ * @param Request $request A Request instance
+ *
+ * @return bool true if one surrogate has Surrogate capability, false otherwise
+ */
+ public function hasSurrogateCapability(Request $request);
+
+ /**
+ * Adds Surrogate-capability to the given Request.
+ *
+ * @param Request $request A Request instance
+ */
+ public function addSurrogateCapability(Request $request);
+
+ /**
+ * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate.
+ *
+ * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags.
+ *
+ * @param Response $response A Response instance
+ */
+ public function addSurrogateControl(Response $response);
+
+ /**
+ * Checks that the Response needs to be parsed for Surrogate tags.
+ *
+ * @param Response $response A Response instance
+ *
+ * @return bool true if the Response needs to be parsed, false otherwise
+ */
+ public function needsParsing(Response $response);
+
+ /**
+ * Renders a Surrogate tag.
+ *
+ * @param string $uri A URI
+ * @param string $alt An alternate URI
+ * @param bool $ignoreErrors Whether to ignore errors or not
+ * @param string $comment A comment to add as an esi:include tag
+ *
+ * @return string
+ */
+ public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '');
+
+ /**
+ * Replaces a Response Surrogate tags with the included resource content.
+ *
+ * @param Request $request A Request instance
+ * @param Response $response A Response instance
+ *
+ * @return Response
+ */
+ public function process(Request $request, Response $response);
+
+ /**
+ * Handles a Surrogate from the cache.
+ *
+ * @param HttpCache $cache An HttpCache instance
+ * @param string $uri The main URI
+ * @param string $alt An alternative URI
+ * @param bool $ignoreErrors Whether to ignore errors or not
+ *
+ * @return string
+ *
+ * @throws \RuntimeException
+ * @throws \Exception
+ */
+ public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors);
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php
index 56f68535f1..9b0517d03e 100644
--- a/src/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\HttpKernel\Tests\EventListener;
use Symfony\Component\HttpKernel\HttpCache\Esi;
-use Symfony\Component\HttpKernel\EventListener\EsiListener;
+use Symfony\Component\HttpKernel\EventListener\SurrogateListener;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -27,7 +27,7 @@ class EsiListenerTest extends \PHPUnit_Framework_TestCase
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new Response('foo ');
- $listener = new EsiListener(new Esi());
+ $listener = new SurrogateListener(new Esi());
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'));
$event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response);
@@ -41,7 +41,7 @@ class EsiListenerTest extends \PHPUnit_Framework_TestCase
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new Response('foo ');
- $listener = new EsiListener(new Esi());
+ $listener = new SurrogateListener(new Esi());
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'));
$event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response);
@@ -55,7 +55,7 @@ class EsiListenerTest extends \PHPUnit_Framework_TestCase
$dispatcher = new EventDispatcher();
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$response = new Response('foo');
- $listener = new EsiListener(new Esi());
+ $listener = new SurrogateListener(new Esi());
$dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'));
$event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response);
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
index c509706389..3133e1506a 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php
@@ -23,14 +23,14 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$request = Request::create('/');
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
- $this->assertTrue($esi->hasSurrogateEsiCapability($request));
+ $this->assertTrue($esi->hasSurrogateCapability($request));
$request = Request::create('/');
$request->headers->set('Surrogate-Capability', 'foobar');
- $this->assertFalse($esi->hasSurrogateEsiCapability($request));
+ $this->assertFalse($esi->hasSurrogateCapability($request));
$request = Request::create('/');
- $this->assertFalse($esi->hasSurrogateEsiCapability($request));
+ $this->assertFalse($esi->hasSurrogateCapability($request));
}
public function testAddSurrogateEsiCapability()
@@ -38,10 +38,10 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$esi = new Esi();
$request = Request::create('/');
- $esi->addSurrogateEsiCapability($request);
+ $esi->addSurrogateCapability($request);
$this->assertEquals('symfony2="ESI/1.0"', $request->headers->get('Surrogate-Capability'));
- $esi->addSurrogateEsiCapability($request);
+ $esi->addSurrogateCapability($request);
$this->assertEquals('symfony2="ESI/1.0", symfony2="ESI/1.0"', $request->headers->get('Surrogate-Capability'));
}
@@ -64,10 +64,10 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$response = new Response();
$response->headers->set('Surrogate-Control', 'content="ESI/1.0"');
- $this->assertTrue($esi->needsEsiParsing($response));
+ $this->assertTrue($esi->needsParsing($response));
$response = new Response();
- $this->assertFalse($esi->needsEsiParsing($response));
+ $this->assertFalse($esi->needsParsing($response));
}
public function testRenderIncludeTag()
@@ -100,18 +100,18 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$response = new Response('foo ');
$esi->process($request, $response);
- $this->assertEquals('foo esi->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent());
+ $this->assertEquals('foo surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent());
$this->assertEquals('ESI', $response->headers->get('x-body-eval'));
$response = new Response('foo ');
$esi->process($request, $response);
- $this->assertEquals('foo esi->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
$response = new Response('foo ');
$esi->process($request, $response);
- $this->assertEquals('foo esi->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
}
public function testProcessEscapesPhpTags()
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php
new file mode 100644
index 0000000000..225911e52f
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php
@@ -0,0 +1,209 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Tests\HttpCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpCache\Ssi;
+
+class SsiTest extends \PHPUnit_Framework_TestCase
+{
+ public function testHasSurrogateEsiCapability()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $request->headers->set('Surrogate-Capability', 'abc="SSI/1.0"');
+ $this->assertTrue($ssi->hasSurrogateCapability($request));
+
+ $request = Request::create('/');
+ $request->headers->set('Surrogate-Capability', 'foobar');
+ $this->assertFalse($ssi->hasSurrogateCapability($request));
+
+ $request = Request::create('/');
+ $this->assertFalse($ssi->hasSurrogateCapability($request));
+ }
+
+ public function testAddSurrogateEsiCapability()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $ssi->addSurrogateCapability($request);
+ $this->assertEquals('symfony2="SSI/1.0"', $request->headers->get('Surrogate-Capability'));
+
+ $ssi->addSurrogateCapability($request);
+ $this->assertEquals('symfony2="SSI/1.0", symfony2="SSI/1.0"', $request->headers->get('Surrogate-Capability'));
+ }
+
+ public function testAddSurrogateControl()
+ {
+ $ssi = new Ssi();
+
+ $response = new Response('foo ');
+ $ssi->addSurrogateControl($response);
+ $this->assertEquals('content="SSI/1.0"', $response->headers->get('Surrogate-Control'));
+
+ $response = new Response('foo');
+ $ssi->addSurrogateControl($response);
+ $this->assertEquals('', $response->headers->get('Surrogate-Control'));
+ }
+
+ public function testNeedsEsiParsing()
+ {
+ $ssi = new Ssi();
+
+ $response = new Response();
+ $response->headers->set('Surrogate-Control', 'content="SSI/1.0"');
+ $this->assertTrue($ssi->needsParsing($response));
+
+ $response = new Response();
+ $this->assertFalse($ssi->needsParsing($response));
+ }
+
+ public function testRenderIncludeTag()
+ {
+ $ssi = new Ssi();
+
+ $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', true));
+ $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', false));
+ $this->assertEquals('', $ssi->renderIncludeTag('/'));
+ }
+
+ public function testProcessDoesNothingIfContentTypeIsNotHtml()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $response = new Response();
+ $response->headers->set('Content-Type', 'text/plain');
+ $ssi->process($request, $response);
+
+ $this->assertFalse($response->headers->has('x-body-eval'));
+ }
+
+ public function testProcess()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $response = new Response('foo ');
+ $ssi->process($request, $response);
+
+ $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
+ $this->assertEquals('SSI', $response->headers->get('x-body-eval'));
+ }
+
+ public function testProcessEscapesPhpTags()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $response = new Response('foo <%= "lala" %>');
+ $ssi->process($request, $response);
+
+ $this->assertEquals('foo php die("foo"); ?>= "lala" %>', $response->getContent());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testProcessWhenNoSrcInAnEsi()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $response = new Response('foo ');
+ $ssi->process($request, $response);
+ }
+
+ public function testProcessRemoveSurrogateControlHeader()
+ {
+ $ssi = new Ssi();
+
+ $request = Request::create('/');
+ $response = new Response('foo ');
+ $response->headers->set('Surrogate-Control', 'content="SSI/1.0"');
+ $ssi->process($request, $response);
+ $this->assertEquals('SSI', $response->headers->get('x-body-eval'));
+
+ $response->headers->set('Surrogate-Control', 'no-store, content="SSI/1.0"');
+ $ssi->process($request, $response);
+ $this->assertEquals('SSI', $response->headers->get('x-body-eval'));
+ $this->assertEquals('no-store', $response->headers->get('surrogate-control'));
+
+ $response->headers->set('Surrogate-Control', 'content="SSI/1.0", no-store');
+ $ssi->process($request, $response);
+ $this->assertEquals('SSI', $response->headers->get('x-body-eval'));
+ $this->assertEquals('no-store', $response->headers->get('surrogate-control'));
+ }
+
+ public function testHandle()
+ {
+ $ssi = new Ssi();
+ $cache = $this->getCache(Request::create('/'), new Response('foo'));
+ $this->assertEquals('foo', $ssi->handle($cache, '/', '/alt', true));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testHandleWhenResponseIsNot200()
+ {
+ $ssi = new Ssi();
+ $response = new Response('foo');
+ $response->setStatusCode(404);
+ $cache = $this->getCache(Request::create('/'), $response);
+ $ssi->handle($cache, '/', '/alt', false);
+ }
+
+ public function testHandleWhenResponseIsNot200AndErrorsAreIgnored()
+ {
+ $ssi = new Ssi();
+ $response = new Response('foo');
+ $response->setStatusCode(404);
+ $cache = $this->getCache(Request::create('/'), $response);
+ $this->assertEquals('', $ssi->handle($cache, '/', '/alt', true));
+ }
+
+ public function testHandleWhenResponseIsNot200AndAltIsPresent()
+ {
+ $ssi = new Ssi();
+ $response1 = new Response('foo');
+ $response1->setStatusCode(404);
+ $response2 = new Response('bar');
+ $cache = $this->getCache(Request::create('/'), array($response1, $response2));
+ $this->assertEquals('bar', $ssi->handle($cache, '/', '/alt', false));
+ }
+
+ protected function getCache($request, $response)
+ {
+ $cache = $this->getMock('Symfony\Component\HttpKernel\HttpCache\HttpCache', array('getRequest', 'handle'), array(), '', false);
+ $cache->expects($this->any())
+ ->method('getRequest')
+ ->will($this->returnValue($request))
+ ;
+ if (is_array($response)) {
+ $cache->expects($this->any())
+ ->method('handle')
+ ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response))
+ ;
+ } else {
+ $cache->expects($this->any())
+ ->method('handle')
+ ->will($this->returnValue($response))
+ ;
+ }
+
+ return $cache;
+ }
+}