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;
}
}
Related
I'm using JMSSerializerBundle in my entities definition, and RestBundle's annotations in controllers.
I have an entity with public and admin-protected attributes, let's say
use JMS\Serializer\Annotation as Serializer;
class UserAddress {
/**
* #Serializer\Expose
* #Serializer\Groups(groups={"address:read"})
*/
private $nonSecretAttribute;
/**
* #Serializer\Expose
* #Serializer\Groups(groups={"address:admin-read"})
*/
private $secretAttribute;
}
and a User like :
class User {
// ...
/**
* #ORM\OneToMany(targetEntity="UserAddress", mappedBy="user")
*/
private $addresses;
and my controller looks like
use FOS\RestBundle\Controller\Annotations as Rest;
class UsersController {
/**
* #Rest\Get("/users/{user}/addresses", requirements={"user"="\d+"})
* #Rest\View(serializerGroups={"address:read"})
* #IsGranted("user_read", subject="user")
*/
public function getUsersAddresses(User $user)
{
return $user->getAddresses();
}
}
but how could I add the address:admin-read to the serializer groups here if the logged in user happens to have the ADMIN_ROLE role ?
Is that possible in the #Rest\View annotation ?
Do I have a way to modify the groups in the controller's method, inside a conditional loop verifying my logged in user's roles ?
You should instantiate the fos-rest View manually and add a Context. On that Context you can set the serialization groups at runtime by evaluating the users roles.
Something like this should work:
use FOS\RestBundle\View\View;
use FOS\RestBundle\Context\Context;
class UsersController
{
public function getUsersAddresses(User $user): View
{
$isAdmin = in_array('ROLE_ADMIN', $user->getRoles());
$context = new Context();
$context->setGroups($isAdmin ? ['address:admin-read'] : ['address:read']);
$view = VVV::create()->setContext($context);
return $view
->setContext($context)
->setData($user->getAddresses());
}
}
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']
Am using symfony framework for my application. And to save records in database I want call the $this->getDoctrine()->getManager(); method in my entity class. But when I did that it gave me the error:
Call to undefined method getDoctrine(),
Can some one tell me what is the right way to do this.
My entity class is like:
namespace Acme\SuperbAppBundle\Entity;
use Symfony\Component\DependencyInjection\Container;
use Doctrine\ORM\Mapping as ORM;
class Users
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
* #return Users
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
function __construct($firstName){
$this->setFirstName($firstName);
}
function save(){
$em = $this->getDoctrine()->getManager();
$em->persist($create);
$em->flush();
}
}
And my controller method is like:
public function test(){
$create = new Users('Rajat');
$create->save();
}
Your save method is attempting to call
$this->getDoctrine();
Whereby $this is the current Class, and any other Class it inherits. As it stands, your current Class, User, is standalone, and does not have a getDoctrine() method. If your Class were to extend the Controller Class, it would have access to that method:
class User extends Controller
I believe this simple fix will work, although it probably doesn't make real sense for it to extend Controller, as it is a User Entity, and unrelated to a Controller. A preferred, more advanced method, would be to inject the Doctrine service into the User class.
Ok, first of all Doctrine Entities :
Handle the entity generation and configuration
Declare the operations on the setters and getters.
If you wana save an object into your entity there it's your User, you have two way to store this user:
One:
You can use entity manager to store a user and the entity will help you to create the right object using the seters and getters:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use PATH\TO\Users;
class ExampleController extends Controller
{
public function examplefunction()
{
$em = $this->getDoctrine()->getManager();
$entity = new Users();
$entity->setFirstName('Rajat');
$em->persist($entity);
$em->flush();
}
}
The other way is to create this entry using QueryBuilder but it's a bad way in your case.
Oh, i forgot please delete the save method in your entity Doctrine manager allready implement it.
Your controller probably doesnt extends Symfony\Bundle\FrameworkBundle\Controller\Controller ...
You should have controller defined like this example:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
}
Entity class does not extends ContainerAware / Controller, so you can't call $this->getDoctrine()->getManager(). I don't think your Entity class should extend to a Controller. Because your entity class will become a controller instance just because you want to access the doctrine manager. That's a not good practice. What you can do is inject doctrine manager to your Entity class through services.
I wrote a blog few weeks ago regarding injecting services container and accessing through constructor. You can inject doctrine entity manager in the same way you inject services container. You can take a look at that if you like :- http://anjanasilva.com/blog/injecting-services-in-symfony-2/
Here's a nice question regarding injecting doctrine manager. Make sure you read the answer as well. :- Symfony 2 EntityManager injection in service
And another nice tutorial on injecting custom repository manager instead of injecting the whole entity manager. Which I believe even a good solution. :- http://php-and-symfony.matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/
Hope this helps to increase your understanding about Symfony 2.
Cheers!
With propel we have findOneOrCreate()
Example.
$bookTag = BookTagQuery::create()
->filterByBook($book)
->findOneOrCreate();
In doctrine anywhere in the controller We can do something like that.
...................
$filename='something';
$document_exists = $em->getRepository('DemoBundle:Document')
->findOneBy(array('filename' => $filename));
if (null === $document_exists) {
$document = new Document();
$document->setFilename($filename);
$em->persist($document);
$em->flush();
}
Is there another way to achieve this in Doctrine?
Is it OK to call the Entity Manager inside the Entity Repository?
Any suggestions?
Easiest way is to extend the base repository:
// src/Acme/YourBundle/Entity/YourRepository.php
namespace Acme\YourBundle\Entity;
use Doctrine\ORM\EntityRepository;
class YourRepository extends EntityRepository
{
public function findOneOrCreate(array $criteria)
{
$entity = $this->findOneBy($criteria);
if (null === $entity)
{
$entity = new $this->getClassName();
$entity->setTheDataSomehow($criteria);
$this->_em->persist($entity);
$this->_em->flush();
}
return $entity
}
}
Then tell your entity to use this repository or extend in even further for specific entities:
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="Acme\YourBundle\Entity\YourRepository")
*/
class Product
{
//...
}
and use it in your controller:
$em = $this->getDoctrine()->getManager();
$product = $em->getRepository('AcmeStoreBundle:Product')
->findOrCreate(array('foo' => 'Bar'));
Source: http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
Just be aware of that flush inside the repository as it would flush all unsaved changes in the EntityManager this way.
Have a look at the constructor of Doctrine\ORM\Repository here.
The EntityManager is mandatory for constructing a repository. The manager can - by default - not be accessed directly from a repository object because the property _em and the getter function getEntityManager are protected.
but ... yes, sure it is "OK" to call the EntityManager via the _em property inside a repository. All the other methods like findBy, ... etc. use it aswell and need the entity-manager to work actually :)
/**
* #var EntityManager
*/
protected $_em;
public function __construct($em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
/**
* #return EntityManager
*/
protected function getEntityManager()
{
return $this->_em;
}
You can easily add a findOneOrCreate method to your entity repository or create a generic extended repository including that method.
then you can extend this new base repository whenever you need the method in a concrete entity repository.
I´ve created my own user Bundle, extending FOSUserBundle.
In my UserBundle, I have a memberID field, with its setters and getters.
I can already find users by memberID using EntityManager, and then I could find the user through UserManager matching the username/email/... obtained with that EntityManager query, but...
Is there a way to findUserByMemberID using UserManager?
Thank you.
Thank you for your replies. It seems it´s easier than all this stuff.
OwnUserBundle:
/**
* #ORM\Column(type="string")
*
*/
protected $memberID;
public function getMemberID()
{
return $this->memberID;
}
public function setMemberID($memberID)
{
$this->memberID = $memberID;
}
You can query FOSUserBundle:
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserBy(array('memberID' => '123'));
Then, using method findUserBy(array(*OwnUserBundle_field* => *search_parameter_value*)) you´ll get the user/s instance.
Every query that is "not standard" one has to be written into a repository class
You have also to relate this class with one that represent you data model.
Suppose that your entity is called User, you have to do something like this
/**
* VendorName\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="VendorName\UserBundle\Repository\UserRepository")
*/
class User implements AdvancedUserInterface
{
[...]
This says that every "custom" query for that entity will be fit into that repository class.
Now you have to create the repository
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function findUserByMemberID($id)
{
/* your logic here */
}
[...]
and you can use that in the following way
$userRepo = $this->em->getRepository('VendorUserBundle:User');
$userRepo->findUserByMemberID();
You could extend the UserManager from the FOSUserBundle in your bundle and write your own method. And you could follow these instructions http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes