Передача имени защищенного пользователя слушателю в services.yml в Symfony2
Я использую Gedmo Loggable, чтобы отслеживать изменения, которые пользователи вносят в объекты. Имя пользователя не сохраняется по умолчанию для каждого изменения, но его необходимо предоставить слушателю.
Пример этого можно найти в документации Loggable:
$loggableListener = new Gedmo\Loggable\LoggableListener;
$loggableListener->setAnnotationReader($cachedAnnotationReader);
$loggableListener->setUsername('admin');
$evm->addEventSubscriber($loggableListener);
Это не работает для меня по двум причинам:
- Я регистрирую слушателя в services.yml, а не в контроллере
- Я не хочу хранить заранее известное имя пользователя, как в примере, но имя пользователя, который вошел в систему
Метод setUsername объекта loggableListener ожидает строку или объект с методом getUsername, предоставляющим строку.
Как я могу передать один из них слушателю? Я нашел способ передать security_token, но этого недостаточно. Что у меня сейчас есть, так это:
(...)
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
#- [ setUsername, [ "A fixed value works" ] ]
- [ setUserValue, [ @security.??? ] ]
Я также использую Blameable и нашел хороший обходной путь для аналогичной проблемы (весь слушатель переопределен). Я пытался сделать то же самое для Loggable, но это выглядит немного сложнее.
Основной вопрос: как я могу передать объект безопасности пользователя (или его имя пользователя) слушателю в services.yml?
Обновить
Маттео показал мне, как передать результат функции в качестве параметра слушателю. Это почти решает проблему. Существует еще один сервис, который предоставляет имя пользователя при получении token_storage. Но это означает, что мне нужно передать параметр службе, которая передается в качестве параметра другой службе. Этот пример объяснит:
- [ setUsername, [ "@=service('gedmo.listener.blameable').getUsername( @security.token_storage )" ] ]
Проблема в том, что @ security.token_storage не принимается в этом контексте. Как я могу передать параметр в метод getUsername()?
3 ответа
Вы можете использовать выражения службы Symfony, например, вы можете попробовать следующее:
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
- [ setUserValue, ["@=service('security.token_storage').getToken()->getUser()->getUsername()"] ]
Но в некоторых случаях пользователь может быть null
, так что вы можете условие с ?
(см. документ).
Надеюсь это поможет
Я думаю, что невозможно ввести авторизованного пользователя в сервис. Но вы можете ввести токен в хранилище: @security.token_storage
(до Symfony 2.6 вам придется использовать @security.context
вместо @security.token_storage
) В вашем сервисе (LoggableListener) вы сможете получить аутентифицированное имя пользователя следующим образом:$tokenStorage->getToken()->getUser()->getUsername()
Я столкнулся с той же проблемой и решил ее, создав прослушиватель журнала, как в https://github.com/stof/StofDoctrineExtensionsBundle.
Для этого я создал этот файл:
//src/AppBundle/Listener/LoggerListener
<?php
namespace AppBundle\Listener;
use Gedmo\Loggable\LoggableListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
* LoggerListener
*
* @author Alexandre Tranchant <alexandre.tranchant@gmail.com>
* @author Christophe Coevoet <stof@notk.org>
*/
class LoggerListener implements EventSubscriberInterface
{
/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var LoggableListener
*/
private $loggableListener;
/**
* LoggerListener constructor.
*
* @param LoggableListener $loggableListener
* @param TokenStorageInterface|null $tokenStorage
* @param AuthorizationCheckerInterface|null $authorizationChecker
*/
public function __construct(LoggableListener $loggableListener, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authorizationChecker = null)
{
$this->loggableListener = $loggableListener;
$this->tokenStorage = $tokenStorage;
$this->authorizationChecker = $authorizationChecker;
}
/**
* Set the username from the security context by listening on core.request
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
if (null === $this->tokenStorage || null === $this->authorizationChecker) {
return;
}
$token = $this->tokenStorage->getToken();
if (null !== $token && $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$this->loggableListener->setUsername($token);
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => 'onKernelRequest',
);
}
}
Тогда я объявил этого слушателя в моем services.yml
файл.
services:
#Loggable
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
# KernelRequest listener
app.listener.loggable:
class: AppBundle\Listener\LoggerListener
tags:
# loggable hooks user username if one is in token_storage or authorization_checker
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest}
arguments:
- '@gedmo.listener.loggable'
- '@security.token_storage'
- '@security.authorization_checker'
Как вы можете видеть на этом снимке экрана, он работает нормально: