6

Note: What I'm doing here is embedding controllers <--- see that link for a similar (official) example.

I want to call a controller from a twig template, and have that controller return an array that I can then use throughout the rest of my template.

I can do this with individual variables:

Twig

{% set testVar = render(controller('AppBundle:Test:index')) %}

Controller

class TestController extends Controller
{
    public function testAction()
    {
        return new Response('OH HAI');
    }
}

However, the following throws an exception: ("The Response content must be a string or object implementing __toString(), "array" given.") with the same twig file.

public function testAction()
{
    return new Response(array('test' => 1, 'foo' => 'bar'));
}

This throws the above exception. How can I accomplish that which I seek without creating a dummy, useless extra template for the controller to render?

Jimbo
  • 25,790
  • 15
  • 86
  • 131
  • 2
    What you are trying to do is breaking MVP pattern (you're trying put logic into view layer). My advice is rethink your solution. Maybe if you say more what you want achieve I can say more. Cheers! – Cyprian Jun 04 '13 at 13:47
  • 1
    Thanks for the reply @Cyprian, I did feel wrong doing this so perhaps there is another way. My view is split into 3 twig templates that extend a layout template: **header**, **sidebar** and **content**. The *sidebar* will require dynamic data on each page load (some live stats, for example). I need to have the sidebar contain this data no matter what is in the *content* template. – Jimbo Jun 04 '13 at 13:52
  • My aim was to code the sidebar, retrieving it's own data from a separate controller, and then everything I code in the future is just the content pages and I don't need to duplicate any code to get the stats for the sidebar. – Jimbo Jun 04 '13 at 13:55
  • I would json_encode() the array in the response and use a twig extension to decode it in the view: http://stackoverflow.com/questions/14500698/decoding-json-in-twig – Bgi Jun 04 '13 at 14:10
  • 1
    Ok, I understand - and it's all ok. You should create own controller action for the sidebar and include it in your base template as you did. But instead your sidebar action return anything - it should render whole sidebar view in its own template. So, the call: {% render(controller('AppBundle:Test:index')) %} will produce whole sidebar htmlcode. Am I good thinking what you want? If so, I'll provide wider example in an answer. – Cyprian Jun 04 '13 at 14:11
  • @Cyprian Yes, I was trying to avoid rendering yet another template just to get some variables though. But you have a point, doing it this way, am I still breaking MVP? If so, I want an alternative solution that is good practice :) – Jimbo Jun 04 '13 at 14:14
  • @Bgi Nice idea, can't believe I never thought of `json_encode()`, definitely an option, hmm... – Jimbo Jun 04 '13 at 14:14
  • You should use @Cyprian 's idea, and use a twig file. Your testAction would return $this->render('sidebar.html.twig', array ('foo' => 1, 'bar' => 'test')); and sidebar.html.twig would format the data. Then render(controller(...)) will output your sidebar straight away. – Bgi Jun 04 '13 at 14:19

1 Answers1

6

The standard way to achieve what you want looks something like that.

Lets assume that you have your regular action. Eg.

class TestController extends Controller
{
    public function testAction()
    {
        return $this->render('AppBundle:Test:index.html.twig');
    }
}

And the template:

<html>
    <body>
        {% block sidebar %}
            {{ controller('AppBundle:Test:sidebar') }}
        {% endblock %}
        {% block content %}
            Hello world
        {% endblock %}                    
    </body>
</html>

Next you need create some action for the sidebar. Note that in this way you avoid put any logic into your view layer.

class BaseController extends Controller
{
    public function sidebarAction()
    {
        $status = $this->get('some.status.logic')->retrieveStatus();

        return $this->render('AppBundle:Base:sidebar.html.twig', array(
            'status' => $status,
        ));
    }
}

And your Base/sidebar.html.twig:

<div class="sidebar">
   {{ status.showStatusInfo() }}
</div>

And that's all. You're not breaking MVP in that way, because you still don't have any logic in your view layer (the logic for the sidebar is in BaseController).

Cyprian
  • 11,174
  • 1
  • 48
  • 45
  • 1
    I think I was worrying about too many levels of abstraction, but this seems to be the best course of action. Cheers! – Jimbo Jun 04 '13 at 14:36