37

I am trying to inject the currently logged in user into a service. My goal is to extend some twig functionality to output it based on user preferences. In this example I want to output any date function using the user specific Timezone.

There doesn't seem to be any way to inject the current user into a service, which seems really odd to me. When injecting the security context, it doesn't have a token even if the user is logged in

I am using FOS user bundle.

services:
    ...
    twigdate.listener.request:
        class: App\AppBundle\Services\TwigDateRequestListener
        arguments: [@twig, @security.context]
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }


<?php

namespace App\AppBundle\Services;

use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

class TwigDateRequestListener
{
    protected $twig;

    function __construct(\Twig_Environment $twig, SecurityContext $context) {

        $this->twig = $twig;
        //$this->user = $context->get...;

        var_dump($context); die;
    }

    public function onKernelRequest(GetResponseEvent $event) {
       // $this->twig->getExtension('core')->setDateFormat($user->getProfile()->getFormat());
       // $this->twig->getExtension('core')->setTimeZone($user->getProfile()->getTimezone());
    }
}

output:

object(Symfony\Component\Security\Core\SecurityContext)[325]
  private 'token' => null
  private 'accessDecisionManager' => 
    object(Symfony\Component\Security\Core\Authorization\AccessDecisionManager)[150]
      private 'voters' => 
        array
          0 => 
            object(Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter)[151]
              ...
          1 => 
            object(Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter)[153]
              ...
          2 => 
            object(Symfony\Component\Security\Acl\Voter\AclVoter)[155]
              ...
      private 'strategy' => string 'decideAffirmative' (length=17)
      private 'allowIfAllAbstainDecisions' => boolean false
      private 'allowIfEqualGrantedDeniedDecisions' => boolean true
  private 'authenticationManager' => 
    object(Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager)[324]
      private 'providers' => 
        array
          0 => 
            object(Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider)[323]
              ...
          1 => 
            object(Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider)[149]
              ...
      private 'eraseCredentials' => boolean true
  private 'alwaysAuthenticate' => boolean false

Am I missing something?

n0xie
  • 405
  • 1
  • 4
  • 8

7 Answers7

59

I think that this question deserves an updated answer since 2.6.x+ since the new security component improvements.

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;

class UserDateExtension extends \Twig_Extension
{
    /**
     * @var TokenStorage
     */
    protected $tokenStorage;


    /**
     * @param \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage    $tokenStorage
     */
    public function __construct(TokenStorage $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    public function getUser()
    {
        return $this->tokenStorage->getToken()->getUser();
    }

    public function getFilters()
    {
        return array(
            'user_date' => new \Twig_Filter_Method($this, "formatUserDate"),
        );
    }

    public function formatUserDate($date, $format)
    {
        $user = $this->getUser();
        // do stuff
    }
}

Services.yml

twig.date_extension:
    class: Acme\Twig\SpecialDateExtension
    tags:
        - { name: twig.extension }
    arguments:
        - "@security.token_storage"
Michael Villeneuve
  • 3,933
  • 4
  • 26
  • 40
42

I would use a twig extension for that:

class UserDateExtension extends \Twig_Extension
{
    private $context;

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

    public function getUser()
    {
        return $this->context->getToken()->getUser();
    }

    public function getFilters()
    {
        return array(
            'user_date' => new \Twig_Filter_Method($this, "formatUserDate"),
        );
    }

    public function formatUserDate($date, $format)
    {
        $user = $this->getUser();
        // do stuff
    }

Now in services.xml

    <service id="user_date_twig_extension" class="%user_date_twig_extension.class%">
        <tag name="twig.extension" />
        <argument type="service" id="security.context" />
    </service>

Then in twig you could do:

{{ date | user_date('d/m/Y') }}
miguel_ibero
  • 1,024
  • 7
  • 9
  • Thanks this worked perfectly. I still don't understand why my first example didn't return the user object, but I got it working, and I think a Twig Extension is actually a cleaner approach. Thanks for the quick reply – n0xie Apr 03 '12 at 10:20
  • 2
    Well you were listening for `kernel.request` event. Setting context token by authentication or reading token from session are done after all `kernel.request` listeners are dispatched. If you have listened for `kernel.response` event then you would have get desired token. Also sorry to misread your question. It is a bad day for me :). – Mun Mun Das Apr 03 '12 at 11:12
  • That makes sense. Thanks for the explenation. You learn something every day. – n0xie Apr 05 '12 at 21:18
  • 8
    SecurityContext (security.context) is deprecated since 2.6. Use security.token_storage instead – nutrija Sep 04 '15 at 14:44
  • 1
    In addition to the comment from @nutrija, see: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements – Thomas Kekeisen Feb 08 '17 at 14:44
30

services.yml

my_service:
    class: ...
    arguments:
        - "@=service('security.token_storage').getToken().getUser()"

Service.php

protected $currentUser;

public function __construct($user)
{
    $this->currentUser = $user;
}

http://symfony.com/doc/current/book/service_container.html#using-the-expression-language

Mantas
  • 4,259
  • 2
  • 27
  • 32
Alex
  • 425
  • 1
  • 6
  • 8
4

The user is a bad candidate to be a service.

  • First it is a model not a service
  • Second there is service security.context where you can get user from.

In a twig template you can use app.user. See symfony doc global-template-variables. If you want to show something based on user permissions you can do {{ is_granted('ROLE_USER') }}.

Maksim Kotlyar
  • 3,821
  • 27
  • 31
2

From Symfony 2.6.

You need use @security.token_storage

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class UserDateExtension extends \Twig_Extension
{
/**
 * @var TokenStorageInterface
 */
protected $tokenStorage;


/**
 * @param $tokenStorage TokenStorage
 */
public function __construct(TokenStorage $tokenStorage)
{
    $this->tokenStorage = $tokenStorage;
}

public function getUser()
{
    return $this->tokenStorage->getToken()->getUser();
}

public function getFilters()
{
    return array(
        'user_date' => new \Twig_Filter_Method($this, "formatUserDate"),
    );
}

public function formatUserDate($date, $format)
{
    $user = $this->getUser();
    // do stuff
}

}

And Services.yml

twig.date_extension:
    class: Acme\Twig\SpecialDateExtension
    tags:
        - { name: twig.extension }
    arguments: ["@security.token_storage"]

reference: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements

Chrysweel
  • 363
  • 4
  • 8
1

I would recommend binding a different event, if you use the kernel.controller event, you will have a token and have no problem. The token is not available in kernel.request since Symfony 2.3

I wrote a guide on how to implement User Timezones for Symfony 2.3+ and 2.6+ in Twig on my blog called Symfony 2.6+ User Timezones.

This is vastly superior to using a Twig Extension because you can use the standard date formatting functions in Twig, as well as provide separate backend UTC date, default Twig date timezones and User defined Twig date timezones.

Here is the most important excerpt:

src/AppBundle/EventListener/TwigSubscriber.php

<?php

namespace AppBundle\EventListener;

use AppBundle\Entity\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class TwigSubscriber implements EventSubscriberInterface
{
    protected $twig;
    protected $tokenStorage;

    function __construct(\Twig_Environment $twig, TokenStorageInterface $tokenStorage)
    {
        $this->twig = $twig;
        $this->tokenStorage = $tokenStorage;
    }

    public static function getSubscribedEvents()
    {
        return [
            'kernel.controller' => 'onKernelController'
        ];
    }

    public function onKernelController(FilterControllerEvent $event)
    {
        $token = $this->tokenStorage->getToken();

        if ($token !== null) {
            $user = $token->getUser();

            if ($user instanceof User) {
                $timezone = $user->getTimezone();
                if ($timezone !== null) {
                    $this->twig->getExtension('core')->setTimezone($timezone);
                }
            }
        }
    }
}

Now you can use twig as normal and it uses your User preferences if available.

Finlay Beaton
  • 601
  • 6
  • 15
-9

You can try injecting @service_container and do $this->container->get('security.context')->getToken()->getUser();.

Mun Mun Das
  • 14,992
  • 2
  • 44
  • 43
  • 26
    Injecting the whole container is a bad idea in most cases. Instead you should inject each service you need explicitly. – Elnur Abdurrakhimov Apr 03 '12 at 10:03
  • @elnur, Tell that to [FriendsOfSymfony](https://github.com/FriendsOfSymfony/FOSFacebookBundle/blob/master/Twig/Extension/FacebookExtension.php#L26) :) – Mun Mun Das Apr 03 '12 at 10:07
  • 2
    Twig extensions is one of the exceptions to this rule. – Elnur Abdurrakhimov Apr 03 '12 at 10:07
  • 2
    @elnur, Sorry I was wrong. It seems that the helper service `FacebookExtension` uses have [templating.helper](https://github.com/FriendsOfSymfony/FOSFacebookBundle/blob/master/Resources/config/facebook.xml#L26) tag. Which may conflict with the extension. You are right injecting `@service_container` is a bad Idea. – Mun Mun Das Apr 03 '12 at 11:01
  • 6
    You can inject just `security.context`. You do not need to inject all the service container. – Waiting for Dev... Dec 11 '12 at 17:44
  • 1
    Don't inject the whole container ! – skonsoft Dec 05 '13 at 14:42
  • 3
    @skonsoft, yes you are right. At the time I answered the question I had confusion about service locator anti-pattern. But still I did not update the answer because I wanted to leave trace of my own stupidity at that time :) – Mun Mun Das Dec 06 '13 at 13:55
  • Never inject the whole container!! – pawel.kalisz May 05 '16 at 11:40