How can I define a constructor in Symfony2 controller. I want to get the the logged in user data available in all the methods of my controller, Currently I do something like this in every action of my controller to get the logged in user.
$em = $this->getDoctrine()->getEntityManager("pp_userdata");
$user = $this->get("security.context")->getToken()->getUser();
I want to do it once in a constructor and make this logged in user available on all my actions
For a general solution for executing code before every controller action you can attach an event listener to the kernel.controller event like so:
<service id="your_app.listener.before_controller" class="App\CoreBundle\EventListener\BeforeControllerListener" scope="request">
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
<argument type="service" id="security.context"/>
</service>
Then in your BeforeControllerListener you will check the controller to see if it implements an interface, if it does, you will call a method from the interface and pass in the security context.
<?php
namespace App\CoreBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use App\CoreBundle\Model\InitializableControllerInterface;
/**
* #author Matt Drollette <matt#drollette.com>
*/
class BeforeControllerListener
{
protected $security_context;
public function __construct(SecurityContextInterface $security_context)
{
$this->security_context = $security_context;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
// not a object but a different kind of callable. Do nothing
return;
}
$controllerObject = $controller[0];
// skip initializing for exceptions
if ($controllerObject instanceof ExceptionController) {
return;
}
if ($controllerObject instanceof InitializableControllerInterface) {
// this method is the one that is part of the interface.
$controllerObject->initialize($event->getRequest(), $this->security_context);
}
}
}
Then, any controllers that you want to have the user always available you will just implement that interface and set the user like so:
use App\CoreBundle\Model\InitializableControllerInterface;
class DefaultController implements InitializableControllerInterface
{
/**
* Current user.
*
* #var User
*/
private $user;
/**
* {#inheritdoc}
*/
public function initialize(Request $request, SecurityContextInterface $security_context)
{
$this->user = $security_context->getToken()->getUser();
}
// ....
}
The interface is nothing more than
namespace App\CoreBundle\Model;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
interface InitializableControllerInterface
{
public function initialize(Request $request, SecurityContextInterface $security_context);
}
I'm runnig a bit late, but in a controller you can just access the user:
$this->getUser();
Should be working since 2.1
My approach to this was:
Make an empty Interface InitializableControllerInterface
Make event Listener for
namespace ACMEBundle\Event;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerConstructor
{
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
// not a object but a different kind of callable. Do nothing
return;
}
$controllerObject = $controller[0];
if ($controllerObject instanceof InitializableControllerInterface) {
$controllerObject->__init($event->getRequest());
}
}
}
In your controller add:
class ProfileController extends Controller implements
InitializableControllerInterface
{
public function __init()
{
$this->user = $security_context->getToken()->getUser();
}
And you will be able to get the $this->user in each action.
Regards
Related
I have some trouble since two days to do a query using a UserRepository outside a controller. I am trying to get a user from the database from a class that I named ApiKeyAuthenticator. I want to execute the query in the function getUsernameForApiKey like in the docs. I think I am suppose to use donctrine as a service but I don't get how to do this.
Thanks for you help in advance!
<?php
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_API')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return User::class === $class;
}
}
You have to make your ApiKeyUserProvider a service and inject the UserRepository as a dependency. Not sure if repositories are services in 2.8, so maybe you'll have to inject the EntityManager .
class ApiKeyUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$repository = $this->em->getRepository(User::class);
// ...
Now register your class as a service in your services.yml file
services:
app.api_key_user_provider:
class: AppBundle\Security\ApiKeyUserProvider
arguments: ['#doctrine.orm.entity_manager']
I need to log the last user's activity time, every page load or ajax call counts.
I suppose I need to subscribe to some event, But I just have no idea to which one.
InteractiveLoginEvent mentioned in this answer, to my understanding is fired in the event of the interactive login only. But, given a session could last a week or more, it will make the record way too inaccurate. So I need another event, but which one?
Or, is there an out of the box functionality for this?
A solution could be a listener for KernelEvents::RESPONSE event, ensuring that the user is authenticated.
namespace AppBundle\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class LastActivityListener implements EventSubscriberInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onResponse(FilterResponseEvent $event)
{
$token = $this->tokenStorage->getToken();
if ($token->isAuthenticated()) {
// save last activity for $token->getUser(); in some place.
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onResponse',
];
}
}
Also, you might need inject the storage service to save this record (e.g. EntityManager if Doctrine is available).
The simplest way to do this would be to subscribe to the kernel.controller event, which will run before every controller action, whether normally or via AJAX. It would look like this:
namespace AppBundle\EventSubscriber;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class UserActivityLogSubscriber implements EventSubscriberInterface
{
/** #var TokenStorageInterface **/
private $tokenStorage;
/** #var LoggerInterface **/
private $logger;
/**
* #param TokenStorageInterface $tokenStorage
* #param LoggerInterface $logger
*/
public function __construct(
TokenStorageInterface $tokenStorage,
LoggerInterface $logger
) {
$this->tokenStorage = $tokenStorage;
$this->logger = $logger;
}
public function onKernelController(FilterControllerEvent $event)
{
$actionTime = new \DateTime();
$controller = $event->getController();
if (!is_array($controller) {
return;
}
$action = get_class($controller[0]).'::'.$controller[1];
$token = $this->tokenStorage->getToken();
$user = $token->getUser();
if ($user) {
$logger->info('User: '.$user->getId().' Action: '.$action.' at: '.$now->format('Y-m-d g:i:s');
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}
This is just a simple example logging the controller action to your standard logger. Instead of just outputting to a log, you could inject the EntityManager and log the event time to a last_activity column in the database for example.
You could also do something like make a UserLoggableController controller interface and only perform this action if your controller implements that interface:
Interface:
namespace AppBundle\Controller;
interface UserLoggableController
{
// ...
}
Controller:
class MyController extends Controller implements UserLoggableController
Modified UserActivityLogSubscriber:
if (!$controller[0] instanceof UserActivityLogSubscriber) {
return;
}
Symfony also has some nice documentation on setting up controller before/after filters.
All of my query in Entity Repository needs to be filtered by user.
Now I want to know how can I access the currently logged in user in Entity Repository directly.
What I did today is to get the currently logged in user in my controller, through the use of $this->getUser() and then pass it to Entity Repository and this is not efficient.
You need to inject security.token_storage service into another one to get the current user, but as of Repository classes belong to Doctrine project, not Symfony, it is not recommended to do this.. May be there is a way to achieve it by creating custom entityManager class as described here, but I don't think it would a good solution..
Instead of customizing an entityManager better create a service which calls repository classes' methods, inject desired services into it.. Let Repository classes do their job.
Implementation would be something like this:
RepositoryClass:
class MyRepository extends EntityRepository
{
public function fetchSomeDataByUser(UserInterface $user)
{
// query
}
}
Service:
class MyService
{
private $tokenStorage;
public function _construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
// other services
}
public function getSomeDataByUser()
{
$user = $this->tokenStorage->getToken()->getUser();
return $this->entityManager->getRepository(MyREPOSITORY)->fetchSomeDataByUser($user);
}
}
Usage:
public function someAction()
{
$dataByUser = $this->get(MYSERVICE)->getSomeDataByUser();
}
If you use JMSDiExtraBundle it can be done by adding setter injection:
use Doctrine\ORM\EntityRepository;
use JMS\DiExtraBundle\Annotation as DI;
class YourRepository extends EntityRepository
{
/** #var User current user entity */
protected $user;
/**
* #DI\InjectParams({
* "token_storage" = #DI\Inject("security.token_storage")
* })
*/
public function setSimplaManager(TokenStorageInterface $tokenStorage)
{
$token = $tokenStorage->getToken();
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
$this->user = $user;
}
}
Can somebody show me how I would inject validator into a regular class using dependency injection.
In my controller I have :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Form;
class FormController extends Controller {
public function indexAction()
{
$form = new Form();
$email = $request->request->get('email');
$valid = $form->isValid($email);
}
}
and I want to use this custom class - but I need it got have access to validator.
class Form {
public function isValid($value)
{
// This is where I fail
$validator = $this->get('validator');
... etc
}
}
To do this, your custom class will need to be defined as a service, and you'll access it from the controller using $form = $this->get('your.form.service'); instead of instantiating it directly.
When defining your service, make sure to inject the validator service:
your.form.service:
class: Path\To\Your\Form
arguments: [#validator]
Then you'll need to handle this in the construct method of your Form service:
/**
* #var \Symfony\Component\Validator\Validator
*/
protected $validator;
function __construct(\Symfony\Component\Validator\Validator $validator)
{
$this->validator = $validator;
}
From Symfony2.5 on
Validator is called RecursiveValidator so, for injection
use Symfony\Component\Validator\Validator\RecursiveValidator;
function __construct(RecursiveValidator $validator)
{
$this->validator = $validator;
}
In Symfony 4+, if you use the default configuration (with auto wiring enabled), the easiest way is to inject a ValidatorInterface.
For instance:
<?php
use Symfony\Component\Validator\Validator\ValidatorInterface;
class MySuperClass
{
private $validator;
public function __construct(
ValidatorInterface $validator
) {
$this->validator = $validator;
}
Your Form class can inherit from ContainerAware (Symfony\Component\DependencyInjection\ContainerAware). Then you will have access to the container and you will be able to get the validator service like this:
$validator = $this->container->get('validator');
I'm creating a website thanks to Symfony2 with FOSUserBundle.
I'm triyng to deny multiple connections on the same login (but from different computers for example).
I've 2 solutions :
Create an event listner on authentification but I didn't manage to make it. (even with the cookbook).
override the login_check method but my FOSUserBundle doesn't work if I do it.
Do you have any better options?
Or any solutions?
Got it finaly. There is just one last update to make to solve it all.
You need to add an other field to the User entity. sessionId (string).
Then update your LoginListener class like that :
// YourSite\UserBundle\Listener\YourSiteLoginListener.php
//...
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();
$user = $event->getAuthenticationToken()->getUser();
$has_session = is_file ( '/path_to_your_php_session_file/'.'sess_'.$user->getSessionId() );
if($user->getLogged() && $has_session){
throw new AuthenticationException('this user is already logged');
}else{
$user->setLogged(true);
$user->setSessionId($session->getId());
$this->userManager->updateUser($user);
}
}
Maybe this will help people to solve this problem.
It's kind of a solution but there is still a problem :
If the user session is killed by php (after too mush time without action for example), you will have to go into your database to reset the "logged" value to 0.
So my solution is :
-add the field "logged" (boolean) to you User entity.
-in YourSite\UserBundle\Listener create a : YourSiteLoginListener.php with this code
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContext;
class YourSiteLoginListener
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if($user->getLogged()){
throw new AuthenticationException('this user is already logged');
}else{
$user->setLogged(true);
$this->userManager->updateUser($user);
}
}
}
-then in the same directory, create a logout handler : YourSiteLogoutHandler.php
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
class YourSiteLogoutHandler implements LogoutHandlerInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function logout (Request $request, Response $response, TokenInterface $token){
$user = $token->getUser();
if($user->getLogged()){
$user->setLogged(false);
$this->userManager->updateUser($user);
}
}
}
-finaly declare those services in your app/config.yml for example:
services:
yoursite_login_listener:
class: YourSite\UserBundle\Listener\YourSiteLoginListener
arguments: [#fos_user.user_manager]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method :onSecurityInteractiveLogin }
yoursite_logout_handler:
class: YourSite\UserBundle\Listener\YourSiteLogoutHandler
arguments: [#fos_user.user_manager]
In Symfony3, the logout handler was not trigged by the code above.
I rebuild the code so the system is updated when the user is logging out.
namespace YourSite\UserBundle\Listener;
use FOS\UserBundle\Model\UserManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public function onLogoutSuccess(Request $request){
global $kernel;
$user = $kernel->getContainer()->get('security.token_storage')->getToken()->getUser();
if($user->getLogged()){
$user->setLogged(false);
$this->userManager->updateUser($user);
}
$referer = $request->headers->get('referer');
return new RedirectResponse($referer);
}
}