2
votes

Ok, I was trying to create twig extension with dependencies on other service (security.context) and got some troubles. So, here is my service declaration:

acme.twig.user_extension:
        class: Acme\BaseBundle\Twig\UserExtension
        arguments: ["@security.context"]
        tags:
            - { name: twig.extension }

and here's my class

// acme/basebundle/twig/userextension.php

namespace Acme\BaseBundle\Twig;

use Symfony\Component\Security\Core\SecurityContext;
use Acme\UserBundle\Entity\User;

class UserExtension extends \Twig_Extension
{

    protected $context;

    public function __construct(SecurityContext $context){
        $this->context = $context;
    }

    public function getFunctions()
    {
        return array(
            'getAbcData' => new \Twig_SimpleFunction('getAbcData', $this->getAbcData()),
        );
    }

    public function getAbcData()
    {
        if ( !is_object($user = $this->context->getToken()->getUser()) || !$user instanceof User){ return null; }
        return array(
            'data_array'   => $user->getData(),
        );
    }

    public function getName()
    {
        return 'user_extension';
    }
}

Finally, I have an error:

FatalErrorException: Error: Call to a member function getUser() on a non-object in \src\Acme\BaseBundle\Twig\UserExtension.php line 27

I guess that security.context service is not initialized yet, then i get an error. Could anyone tell, please, is there are ways to load service manually, or any better solutions for an issue? Thanks a lot.


I use Symfony 2.5.*

UPD:

I've also found this notice in symfony docs

Keep in mind that Twig Extensions are not lazily loaded. This means that there's a higher chance that you'll get a CircularReferenceException or a ScopeWideningInjectionException if any services (or your Twig Extension in this case) are dependent on the request service. For more information take a look at How to Work with Scopes. Actually, I have no idea about how to do it correct..

3

3 Answers

2
votes

You are calling $this->getAbcData() when constructing Twig_SimpleFilter. But you have to pass a callable as argument.

public function getFunctions() {
    return array (
        'getAbcData' => new \Twig_SimpleFunction( 'getAbcData', array( $this, 'getAbcData' ))
    );
}

Leo is also right. You should check first if getToken() is returning an object before trying getToken()->getUser().

You can also pass the user to the function as a parameter in twig: {{ getAbcData(app.user) }}. This way the function is more generic and could be used for any user, not just the currently logged in one.

1
votes

This should probably work. The error message means that getToken() is not an object so you have to test if getToken() is an object before testing if getUser() is also is an object.

public function getAbcData()
{
    $token = $this->context->getToken();

    if (!is_object($token) || !is_object($token->getUser())) {
        return null;
    }

    return array(
        'data_array'   => $user->getData(),
    );
}
1
votes

You need to change your twig extension to have the container not the security context passed into the constructor.

Twig_Extensions are special in that the normal rule of don't pass in the container but instead pass in only what you need often doesn't apply as it causes problems due to scope issues.

So change your extension to be like this.

// acme/basebundle/twig/userextension.php

namespace Acme\BaseBundle\Twig;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Acme\UserBundle\Entity\User;

class UserExtension extends \Twig_Extension
{
    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
     */
    protected $container;

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

    public function getFunctions()
    {
        return array(
            'getAbcData' => new \Twig_SimpleFunction('getAbcData', $this->getAbcData()),
        );
    }

    public function getAbcData()
    {
        if ( !is_object($user = $this->container->get('security.context')->getToken()->getUser()) || !$user instanceof User){ return null; }
        return array(
            'data_array'   => $user->getData(),
        );
    }

    public function getName()
    {
        return 'user_extension';
    }
}