Commit 8f05804a authored by Fabien Potencier's avatar Fabien Potencier

feature #803 added support for the Symfony HttpKernel fragment sub-framework (fabpot)

This PR was merged into the master branch.

Discussion
----------

added support for the Symfony HttpKernel fragment sub-framework

Closes #783 and #802

Todo:

 - [x] add some tests

Commits
-------

dcbab7f6 added support for the Symfony HttpKernel fragment sub-framework
parents e0aa6a89 dcbab7f6
Changelog Changelog
========= =========
1.1.2 (2013-XX-XX) 1.2.0 (2013-XX-XX)
------------------ ------------------
* n/a * Deprecated TwigCoreExtension (register the new HttpFragmentServiceProvider instead)
* Added HttpFragmentServiceProvider
1.1.1 (2013-10-11) 1.1.1 (2013-10-11)
------------------ ------------------
......
HttpFragmentServiceProvider
===========================
The *HttpFragmentServiceProvider* provides support for the Symfony2 fragment
sub-framework, which allows you to embed fragments of HTML in a template.
Parameters
----------
* **fragment.path**: The path to use for the URL generated for ESI and
HInclude URLs (``/_fragment`` by default).
* **uri_signer.secret**: The secret to use for the URI signer service (used
for the HInclude renderer).
* **fragment.renderers.hinclude.global_template**: The content or Twig
template to use for the default content when using the HInclude renderer.
Services
--------
* **fragment.handler**: An instance of `FragmentHandler
<http://api.symfony.com/master/Symfony/Component/HttpKernel/Fragment/FragmentHandler.html>`_.
* **fragment.renderers**: An array of fragment renderers (by default, the
inline, ESI, and HInclude renderers are pre-configured).
Registering
-----------
.. code-block:: php
$app->register(new Silex\Provider\HttpFragmentServiceProvider());
Usage
-----
.. note::
This section assumes that you are using Twig for your templates.
Instead of building a page out of a single request/controller/template, the
fragment framework allows you to build a page from several
controllers/sub-requests/sub-templates by using **fragments**.
Including "sub-pages" in the main page can be done with the Twig ``render()``
function:
.. code-block:: jinja
The main page content.
{{ render('/foo') }}
The main page content resumes here.
The ``render()`` call is replaced by the content of the ``/foo`` URL
(internally, a sub-request is handled by Silex to render the sub-page).
Instead of making internal sub-requests, you can also use the ESI (the
sub-request is handled by a reverse proxy) or the HInclude strategies (the
sub-request is handled by a web browser):
.. code-block:: jinja
{{ render(url('route_name')) }}
{{ render_esi(url('route_name')) }}
{{ render_hinclude(url('route_name')) }}
...@@ -14,6 +14,7 @@ Silex ...@@ -14,6 +14,7 @@ Silex
validator validator
form form
http_cache http_cache
http_fragment
security security
serializer serializer
service_controller service_controller
...@@ -16,6 +16,7 @@ use Silex\ServiceProviderInterface; ...@@ -16,6 +16,7 @@ use Silex\ServiceProviderInterface;
use Silex\HttpCache; use Silex\HttpCache;
use Symfony\Component\HttpKernel\HttpCache\Esi; use Symfony\Component\HttpKernel\HttpCache\Esi;
use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\HttpKernel\EventListener\EsiListener;
/** /**
* Symfony HttpKernel component Provider for HTTP cache. * Symfony HttpKernel component Provider for HTTP cache.
...@@ -38,10 +39,15 @@ class HttpCacheServiceProvider implements ServiceProviderInterface ...@@ -38,10 +39,15 @@ class HttpCacheServiceProvider implements ServiceProviderInterface
return new Store($app['http_cache.cache_dir']); return new Store($app['http_cache.cache_dir']);
}); });
$app['http_cache.esi_listener'] = $app->share(function ($app) {
return new EsiListener($app['http_cache.esi']);
});
$app['http_cache.options'] = array(); $app['http_cache.options'] = array();
} }
public function boot(Application $app) public function boot(Application $app)
{ {
$app['dispatcher']->addSubscriber($app['http_cache.esi_listener']);
} }
} }
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Silex\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer;
use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer;
use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer;
use Symfony\Component\HttpKernel\EventListener\FragmentListener;
use Symfony\Component\HttpKernel\UriSigner;
/**
* HttpKernel Fragment integration for Silex.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpFragmentServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['fragment.handler'] = $app->share(function ($app) {
$handler = new FragmentHandler($app['fragment.renderers'], $app['debug'], $app['request_stack']);
// to be removed when 2.3 is not supported anymore
if (null === $app['request_stack']) {
$handler->setRequest($app['request']);
}
return $handler;
});
$app['fragment.renderer.inline'] = $app->share(function ($app) {
$renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']);
$renderer->setFragmentPath($app['fragment.path']);
return $renderer;
});
$app['fragment.renderer.hinclude'] = $app->share(function ($app) {
$renderer = new HIncludeFragmentRenderer(null, $app['uri_signer'], $app['fragment.renderer.hinclude.global_template'], $app['charset']);
$renderer->setFragmentPath($app['fragment.path']);
return $renderer;
});
$app['fragment.renderer.esi'] = $app->share(function ($app) {
$renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline']);
$renderer->setFragmentPath($app['fragment.path']);
return $renderer;
});
$app['fragment.listener'] = $app->share(function ($app) {
return new FragmentListener($app['uri_signer'], $app['fragment.path']);
});
$app['uri_signer'] = $app->share(function ($app) {
return new UriSigner($app['uri_signer.secret']);
});
$app['uri_signer.secret'] = md5(__DIR__);
$app['fragment.path'] = '/_fragment';
$app['fragment.renderer.hinclude.global_template'] = null;
$app['fragment.renderers'] = $app->share(function ($app) {
$renderers = array($app['fragment.renderer.inline'], $app['fragment.renderer.hinclude']);
if (isset($app['http_cache.esi'])) {
$renderers[] = $app['fragment.renderer.esi'];
}
return $renderers;
});
}
public function boot(Application $app)
{
$app['dispatcher']->addSubscriber($app['fragment.listener']);
}
}
...@@ -18,6 +18,8 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; ...@@ -18,6 +18,8 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
* Twig extension. * Twig extension.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated deprecated since 1.2, will be removed in 1.3. Use HttpFragmentServiceProvider instead
*/ */
class TwigCoreExtension extends \Twig_Extension class TwigCoreExtension extends \Twig_Extension
{ {
......
...@@ -18,6 +18,7 @@ use Symfony\Bridge\Twig\Extension\RoutingExtension; ...@@ -18,6 +18,7 @@ use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Extension\SecurityExtension; use Symfony\Bridge\Twig\Extension\SecurityExtension;
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Form\TwigRenderer; use Symfony\Bridge\Twig\Form\TwigRenderer;
...@@ -46,7 +47,6 @@ class TwigServiceProvider implements ServiceProviderInterface ...@@ -46,7 +47,6 @@ class TwigServiceProvider implements ServiceProviderInterface
$twig = new \Twig_Environment($app['twig.loader'], $app['twig.options']); $twig = new \Twig_Environment($app['twig.loader'], $app['twig.options']);
$twig->addGlobal('app', $app); $twig->addGlobal('app', $app);
$twig->addExtension(new TwigCoreExtension());
if ($app['debug']) { if ($app['debug']) {
$twig->addExtension(new \Twig_Extension_Debug()); $twig->addExtension(new \Twig_Extension_Debug());
...@@ -65,6 +65,15 @@ class TwigServiceProvider implements ServiceProviderInterface ...@@ -65,6 +65,15 @@ class TwigServiceProvider implements ServiceProviderInterface
$twig->addExtension(new SecurityExtension($app['security'])); $twig->addExtension(new SecurityExtension($app['security']));
} }
if (isset($app['fragment.handler'])) {
$app['fragment.renderer.hinclude']->setTemplating($twig);
$twig->addExtension(new HttpKernelExtension($app['fragment.handler']));
} else {
// fallback for BC, to be removed in 1.3
$twig->addExtension(new TwigCoreExtension());
}
if (isset($app['form.factory'])) { if (isset($app['form.factory'])) {
$app['twig.form.engine'] = $app->share(function ($app) { $app['twig.form.engine'] = $app->share(function ($app) {
return new TwigRendererEngine($app['twig.form.templates']); return new TwigRendererEngine($app['twig.form.templates']);
......
<?php
/*
* This file is part of the Silex framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Silex\Tests\Provider;
use Silex\Application;
use Silex\Provider\HttpCacheServiceProvider;
use Silex\Provider\HttpFragmentServiceProvider;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\Request;
class HttpFragmentServiceProviderTest extends \PHPUnit_Framework_TestCase
{
public function testRenderFunction()
{
$app = new Application();
$app->register(new HttpFragmentServiceProvider());
$app->register(new HttpCacheServiceProvider(), array('http_cache.cache_dir' => sys_get_temp_dir()));
$app->register(new TwigServiceProvider(), array(
'twig.templates' => array(
'hello' => '{{ render("/foo") }}{{ render_esi("/foo") }}{{ render_hinclude("/foo") }}',
'foo' => 'foo',
),
));
$app->get('/hello', function () use ($app) {
return $app['twig']->render('hello');
});
$app->get('/foo', function () use ($app) {
return $app['twig']->render('foo');
});
$response = $app['http_cache']->handle(Request::create('/hello'));
$this->assertEquals('foofoo<hx:include src="/foo"></hx:include>', $response->getContent());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment