Prisoner
Prisoner

Reputation: 27618

Load view without rendering

I have a Twig extension in my Symfony project which renders a twitter widget. Currently, in my extension, I have this:

public function twitter($handle, $number = 5)
{
    return "<div data-tweet-row=\"&lt;div&gt;&lt;a  href='http://twitter.com/{screen_name}/status/{id}'&gt;{tweet}&lt;/a&gt;&lt;span class='datetime'&gt;{datetime}&lt;/span&gt;&lt;/div&gt;\" data-twitter-header=\"&lt;div class='header'&gt;&lt;img src='https://api.twitter.com/1/users/profile_image?screen_name={screen_name}&amp;size=normal' /&gt;&lt;h4&gt;&lt;a href='http://twitter.com/{screen_name}'&gt;@{screen_name}&lt;/a&gt;&lt;/h4&gt;&lt;/div&gt;\" id=\"twitter\">&nbsp;</div><script>$('#twitter').biff_twitter({screen_name:'$handle',count:$number});</script>";
}

However I really don't like this solution, I'd rather have the HTML saved under views and then load the file from within my extension.

I have access to the container from within the extension using:

private $container;

public function __construct(ContainerInterface $container){
    $this->container = $container;
}

So I need something like

$view_file = $this->container->get('...')->view('InternalSocialBundle:twitter_placeholder.html.twig')

Upvotes: 1

Views: 1292

Answers (2)

Prisoner
Prisoner

Reputation: 27618

Ok, so I resolved the issue that I had but it was sort of my fault for not testing this fully. @Cyprian's solution does indeed work absolutely fine when you're not doing anything complicated and just using the extension as intended.

My problem was that I was using this evaluate extension which was loading a template from the database, therefore it was twig that was doing all of the loading (and therefore was only interpreting the first param of $this->render() as a string - it made no attempt to load it using the symfony naming pattern).

This means my extension would work fine when using the above method in an actual twig file, but not when I used the {{body|evaluate}} extension.

The solution to this problem was as follows:

In my services.yml file, not only did I need service_container, but also twig.loader so it looked something liked:

class: Test\SocialBundle\Twig\SocialExtension
arguments: [@service_container,@twig.loader]
tags:
    -  { name: 'twig.extension' }

I then have this as the twig extensions constructor:

public function __construct(ContainerInterface $container, \Symfony\Bundle\TwigBundle\Loader\FilesystemLoader $loader)
{
    $this->container = $container;
    $this->loader = $loader;
}

In my getFilters() definition:

public function getFilters()
{
  return array(
    'twitter' => new \Twig_Filter_Method($this, 'twitter',array(
        'is_safe' => array(
            'evaluate' => true
            ),
        'needs_environment' => true,
        )
    )
  )
}

and finally, before I returned the rendered output:

public function twitter(\Twig_Environment $environment, $id)
{
    $social_item = array(); // get my data here from my service;
    $environment->setLoader($this->loader);
    return $environment->render("TestSocialBundle:Front:twitter_placeholder.html.twig",array());
}

This ensured that I was always using the \Symfony\Bundle\TwigBundle\Loader\FilesystemLoader loader, and not \Twig_Loader_String which was being used in my case.

Upvotes: 1

Cyprian
Cyprian

Reputation: 11374

Of course you have totally right. Name of the service you are looking for is templating. So this will work for You:

$html = $this->container->get('templating')->render('InternalSocialBundle:twitter_placeholder.html.twig');

edit:

This is an working example:

class FooExtension extends \Twig_Extension
{
    private $container;

    public function setContainer(\Symfony\Component\DependencyInjection\Container $container)
    {
        $this->container = $container;
    }

    public function foo()
    {
        return $this->container->get('templating')->render('CogiPortfolioBundle:Portfolio:tmp.html.twig');
    }

    public function getFunctions()
    {
        return array(
            'foo' => new \Twig_Function_Method($this, 'foo', array('is_safe' => array('html')))
        );
    }

    /* (...) */
}

The service definition:

<service id="cogi.foo.ext" class="Cogi\PortfolioBundle\TwigExtension\FooExtension">
    <call method="setContainer">
        <argument type="service" id="service_container" />
    </call>
    <tag name="twig.extension" />
</service>

And know if I call in any template {{ foo() }} it renders html from tmp.twig.html. And on the other side - if in my extension I put "path" to non existing template in render method then an exception is thrown.

I hope this helps.

Upvotes: 1

Related Questions