4
votes

Use case
I am developing a CMF on top of Symfony2. One of the features will be the support of "widgets": an possibility for end users to add small 'blocks' or 'modules' to a page. Examples:

  • A small login form
  • A list of products
  • Some photo's from a gallery
  • A shopping cart

The idea is that most of those widgets will link to to normal, full page Routes/Controllers.

For example: a user want a list of popular products in a sidebar on a content page. The items will link to the normal /product/{name} route of the ProductController. But the list in this case would be a widget. The end user can define where it must be placed, and for example, how much items must be shown.

The behavior of the 'widgets' is the same as regular Symfony2 controllers, it has routes, actions, it renders a view, etcetera. There is a WidgetManager with a catch-all route to load the widgets, configure them and render them in the right place.

I have not that much experience with Symfony2, but I am playing wit it now for more then 3 months. I definitely want to stay with Symfony2, but will need to add some magic to realize some of my ideas.

Question
What is the best way to support rendering multiple controllers (widgets) in one request?

Research
Symfony's TwigExtension "ActionExtension" contains a "render" method, which contains the basic idea:

<div id="sidebar">
    {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %}
</div>

(Documentation: http://symfony.com/doc/current/book/templating.html#embedding-controllers)

But it is quite limited. Some problems with this approach:

  • I cannot configure the 'widgets' before rendering them (for example: $myWidget->set('show_toolbar', false)), I don't want to pass all options as controller action parameters.
  • It is not possible to use template inheritance. I need this for example for 'injecting' the asset references (javascript/css) in de base <HEAD> block.

What I want

I want the following code to work (this is a simplified example):

// Serius\PageBundle\Controller\PageController.php

// executed by a catch-all route
public function indexAction($url) {
    // load CMS page, etc
    
    $widgets = $this->loadWidgets($page); // widgets configuration is stored in database
    // at this point, $widgets is an array of Controller *instances*
    // meaning, they are already constructed and configured

    return $this->render("SeriusPageBundle:Page:content.html.twig", array(
        'widgets' => $widgets
    ));   
}

Serius\PageBundle\Resources\views\Page\content.html.twig

{% extends 'SeriusPageBundle::layout.html.twig' %}

{% block content %}
    {% for widget in widgets %}
    <div>
        {% render widget %}
        <!-- Of course, this doesn't work, I would have to create my own Twig extension -->
    </div>
    {% endfor %}
{% endblock %}

An example of a widget template:

{% extends '::base.html.twig' %}
{% block stylesheets %}
  My stylesheets
{% endblock %}

{% block body %}
    This is a shoppingcart widget!
{% endblock %}

How can I achieve this? Do someone have experience with anything like this? I already looked at the Symfony CMF project, but it has no support for this (as far as I could find out).

2

2 Answers

1
votes

I have something similar going around and I think that this code will help you. In chosen template you get the variables with the names of blocks.

public function render()
{       
    $modules = $this->moduleService->getModules();

    foreach($modules as $m){
        $templateName = $m->getTemplateName();

        $template = $this->twig->loadTemplate($templateName);
        $blockNames = $template->getBlockNames();

        foreach($blockNames as $b){
            if(isset($this->blocks[$b]) == false)
                $this->blocks[$b] = '';
            $this->blocks[$b] .= $template->renderBlock($b, array('a' => 'aaa', 'b' => 'bbb'));
        }

    }
    $content = $this->twig->render('Admin/index.html.twig',$this->blocks);
    return new \Symfony\Component\HttpFoundation\Response($content);
}
1
votes

I know this is old, but if anyone is looking for something like this the SonataBlockBundle might be your solution.

https://github.com/sonata-project/SonataBlockBundle