ParamConverter: inject curent user in Repository - symfony

In a view, I want to display linked values, but all of the linked values can't be displayed because they depends to the user access.
To do that I just need to do a leftJoin with a ->where('user', $user). The question is... how can I inject the current user in the Repository from the ParamConverter ?

Assuming you are using Doctrine, this should work;
In your controller;
$objRepo = $this->getDoctrine()->getManager()->getRepository('AppBundle:Objects');
$files = $this->objRepo->getAllForUserId($this->getUser()->getId());
And in your repo file;
public function getAllForUserId($user_id, $limit=100)
{
if (null === $user_id) {
throw new ORMInvalidArgumentException('User id not set');
}
$queryBuilder = $this->createQueryBuilder('obj');
$queryBuilder->select(array('obj', 'usr'))
->innerJoin('obj.users', 'usr')
->where('usr.id = :user_id')
->setParameter(':user_id', $user_id)
->orderBy('file.created', Criteria::DESC);
$query = $queryBuilder->getQuery();
return $query->getResult();
}

Related

Get related record one to many relationships Symfony2

I want to check if the current user has already a record for the current date.The User entity has many Timerecord and Timerecord has one User. So far,
This two entities resides in two different bundles
Project
UserBundle
TimerecordBundle
controller
$user = $this->container->get('security.context')->getToken()->getUser();
$userr = $user->getUsername();
$alreadyLoggedIn = $em->getRepository('EmployeeBundle:Timerecord')->findAlreadyTimedInToday($userr);
var_dump($alreadyLoggedIn);
die();
Respository
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User u')
->join('u.Timerecord t')
->where('u.username LIKE :currentuser')
->setParameter('currentuser',$userr)
->getQuery()
->getSingleResult()
;
}
I got this exception
Warning: Missing argument 2 for Doctrine\ORM\QueryBuilder::from()
How do you fetch the related user in this case?
The alias for the from part should be given to the second argument.
Change your method to this:
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User', 'u')
->join('u.Timerecord', 't')
->where('u.username LIKE :currentuser')
->setParameter('currentuser', $userr)
->getQuery()
->getSingleResult()
;
}

Symfony call get by Name from variable

I would like to call a getter with the stored fieldname from the database.
For example, there are some fieldnames store like ['id','email','name'].
$array=Array('id','email','name');
Normally, I will call ->getId() or ->getEmail()....
In this case, I have no chance to handle things like this. Is there any possibility to get the variable as part of the get Command like...
foreach ($array as $item){
$value[]=$repository->get$item();
}
Can I use the magic Method in someway? this is a bit confusing....
Symfony offers a special PropertyAccessor you could use:
use Symfony\Component\PropertyAccess\PropertyAccess;
$accessor = PropertyAccess::createPropertyAccessor();
class Person
{
private $firstName = 'Wouter';
public function getFirstName()
{
return $this->firstName;
}
}
$person = new Person();
var_dump($accessor->getValue($person, 'first_name')); // 'Wouter'
http://symfony.com/doc/current/components/property_access/introduction.html#using-getters
You can do it like this :
// For example, to get getId()
$reflectionMethod = new ReflectionMethod('AppBundle\Entity\YourEntity','get'.$soft[0]);
$i[] = $reflectionMethod->invoke($yourObject);
With $yourObject being the object of which you want to get the id from.
EDIT : Don't forget the use to add :
use ReflectionMethod;
Hope this helps.
<?php
// You can get Getter method like this
use Doctrine\Common\Inflector\Inflector;
$array = ['id', 'email', 'name'];
$value = [];
foreach ($array as $item){
$method = Inflector::classify('get_'.$item);
// Call it
if (method_exists($repository, $method))
$value[] = $repository->$method();
}

Return entity record value based on custom value in Symfony2

I'm making an API and I need to display data from entity based on action type. For example, I have User and his visibility preferences (to hide/show his name for other people). Doing this like that:
<?php
// entity
public function getSurname()
{
$visibility = $this->getVisibility();
if($visibility['name'] == 0)
return $this->surname;
return '';
}
is ok, but if User is logged in, I want to show him his name, for example, in edit account.
The best way I think is to edit record when I get it from database, but how to this on doctrine object?
<?php
//controller
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle:User')->findOneById($id);
$user = $this->getVisibility();
if($user != $this->getUser() && $visibility['name'] == 0)
$user->setSurname(''); //but this save this to DB, not to "view"
UPDATE
Unfortunately (or I'm doing something wrong) my problem can't be solved by Snake answer, beause when I do this code:
<?php
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle')-findOneById($id);
return array(
self::USER => $user
);
In my API response, entity modifications don't work, because I think this is getting record directly from DB? And I need return whole object like in code above.
UPDATE2
I found workaround for this
<?php
// entity
/**
* #ORM\PostLoad
*/
public function postLoad() {
$this->surname = $this->getSurname();
}
and then I can just return full $user object
If you want to show the surname depends of visibility, you can add the Symfony\Component\Security\Core\User\EquatableInterface and edit your function:
// entity
public function getSurname(Acme\DemoBundle\User $user = null)
{
// Nothing to compare or is the owner
if( !is_null( $user ) && $this->isEqualTo($user) ){
return $this->surname;
}
// else...
$visibility = $this->getVisibility();
if($visibility['name'] == 0)
return $this->surname;
return '';
}
After in your controller you only must get the surname:
//controller
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle:User')->findOneById($id);
// If the user is the owner, show the surname, otherwise it shows the surname depends of visibility
$surname = $user->getSurname( $this->getUser() );
Also, you can execute the logic in the controller (check if is the same user and get the visibility...).
I suggest you read about ACL too.

unique slugs with multi entities in symfony2

I have a small symfony2 application where a user can create pages. Each page should be accessible via the route /{user_slug}/{page_slug}. I have the entities user and page and I use the sluggable behavior for both entities. In order to find the correct page the combination of user_slug and page_slug has to be unique.
What is the best way to check that the combination of user_slug and page_slug is uniqe?
Try this in your prepository:
public function findByUsernameAndSlug($username, $slug)
{
$em = $this->getEntityManager();
$query = $em->createQuery("
SELECT g
FROM Acme\PagesBundle\Entity\Page p
JOIN p.owner u
WHERE u.username = :username
AND p.slug = :slug
")
->setParameter('username', $username)
->setParameter('slug', $slug);
foreach ($query->getResult() as $goal) {
return $goal;
}
return null;
}
Before you persist the entity in your service-layer check whether given combination of user and page slug are unique, if not modify the page slug (append -2 or something like that) or throw an exception:
public function persistPage(Page $page) {
$userSlug = $page->getUser()->getSlug();
$pageSlug = $page->getSlug();
if ($this->pagesRepository->findOneBySlugs($userSlug, $pageSlug) != null) {
// given combination already exists
throw new NonUniqueNameException(..);
// or modify the slug
$page->setSlug($page->getSlug() . '-2');
return $this->persistPage($page);
}
return $this->em->persist($page);
}
// PagesRepository::findOneBySlugs($userSlug, $pageSlug)
public function findOneBySlugs($userSlug, $pageSlug) {
$query = $this->em->createQueryBuilder('p')
->addSelect('u')
->join('p.user', 'u')
->where('p.slug = :pageSlug')
->where('u.slug = :userSlug;)
->getQuery();
$query->setParameters(combine('userSlug', 'pageSlug'));
return $query->getSingleResult();
}

How to use class-scope aces in Symfony2?

I've got a problem with class-scope aces. I've created an ace for a
class like this :
$userIdentity = UserSecurityIdentity::fromAccount($user);
$classIdentity = new ObjectIdentity('some_identifier', 'Class\FQCN');
$acl = $aclProvider->createAcl($classIdentity);
$acl->insertClassAce($userIdentity, MaskBuilder::MASK_CREATE);
$aclProvider->updateAcl($acl);
Now, I'm trying to check the user's permissions. I've found this way
of doing things, which is not documented, but gives the expected
results on a class basis :
$securityContext->isGranted('CREATE', $classIdentity); // returns true
$securityContext->isGranted('VIEW', $classIdentity); // returns true
$securityContext->isGranted('DELETE', $classIdentity); // returns false
This method is well adapated to the "CREATE" permission check, where
there's no available object instance to pass to the method. However,
it should be possible to check if another permission is granted on a
particular instance basis :
$entity = new Class\FQCN();
$em->persist($entity);
$em->flush();
$securityContext->isGranted('VIEW', $entity); // returns false
This is where the test fails. I expected that an user who has a given
permission mask on a class would have the same permissions on every
instance of that class, as stated in the documentation ("The
PermissionGrantingStrategy first checks all your object-scope ACEs if
none is applicable, the class-scope ACEs will be checked"), but it
seems not to be the case here.
you are doing it right. and according to the bottom of this page, it should work, but it does not.
the easiest way to make it work is creating an AclVoter class:
namespace Core\Security\Acl\Voter;
use JMS\SecurityExtraBundle\Security\Acl\Voter\AclVoter as BaseAclVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Doctrine\Common\Util\ClassUtils;
class AclVoter extends BaseAclVoter
{
public function vote( TokenInterface $token , $object , array $attributes )
{
//vote for object first
$objectVote = parent::vote( $token , $object , $attributes );
if( self::ACCESS_GRANTED === $objectVote )
{
return self::ACCESS_GRANTED;
}
else
{
//then for object's class
$oid = new ObjectIdentity( 'class' , ClassUtils::getRealClass( get_class( $object ) ) );
$classVote = parent::vote( $token , $oid , $attributes );
if( self::ACCESS_ABSTAIN === $objectVote )
{
if( self::ACCESS_ABSTAIN === $classVote )
{
return self::ACCESS_ABSTAIN;
}
else
{
return $classVote;
}
}
else if( self::ACCESS_DENIED === $objectVote )
{
if( self::ACCESS_ABSTAIN === $classVote )
{
return self::ACCESS_DENIED;
}
else
{
return $classVote;
}
}
}
return self::ACCESS_ABSTAIN;
}
}
then in security.yml set this:
jms_security_extra:
voters:
disable_acl: true
and finally set up the voter as a service:
core.security.acl.voter.basic_permissions:
class: Core\Security\Acl\Voter\AclVoter
public: false
arguments:
- '#security.acl.provider'
- '#security.acl.object_identity_retrieval_strategy'
- '#security.acl.security_identity_retrieval_strategy'
- '#security.acl.permission.map'
- '#?logger'
tags:
- { name: security.voter , priority: 255 }
- { name: monolog.logger , channel: security }
You need to ensure each object has its own ACL (use $aclProvider->createAcl($entity)) for class-scope permissions to work correctly.
See this discussion: https://groups.google.com/forum/?fromgroups=#!topic/symfony2/pGIs0UuYKX4
If you don't have an existing entity, you can check against the objectIdentity you created.
Be careful to use "double-backslashes", because of the escaping of the backslash.
$post = $postRepository->findOneBy(array('id' => 1));
$securityContext = $this->get('security.context');
$objectIdentity = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');
// check for edit access
if (true === $securityContext->isGranted('EDIT', $objectIdentity)) {
echo "Edit Access granted to: <br/><br/> ";
print_r("<pre>");
print_r($post);
print_r("</pre>");
} else {
throw new AccessDeniedException();
}
That should work!
If you would check for "object-scope" you could just use $post instead of $objectIdentity in the isGranted function call.
I have tried to find the best solution for this problem and I think the best answer is the one of rryter / edited by Bart. I just want to extend the solution.
Let's say you want to give access to a specific object type for a specific user, but not for a concrete object instance (for example id=1).
Then you can do the following:
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = new ObjectIdentity('class', 'someNamspace\\SeperatedByDoubleSlashes');
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$securityContext = $this->get('security.context');
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
$securityContext = $this->get('security.context');
// check for edit access
if (false === $securityContext->isGranted('EDIT', $objectIdentity)) {
throw new AccessDeniedException();
}
The difference to the example given by the symfony cookbook is that you are using the class scope and not the object scope.
There's only 1 line that makes the difference:
$objectIdentity = new ObjectIdentity('class', 'someNamspace\\SeperatedByDoubleSlashes');
instead of:
$objectIdentity = ObjectIdentity::fromDomainObject($object);
You can still add specific permissions for specific object instances if you have at least one class scope permission in your classes acl.

Resources