Symfony - Acces only to their own customers - symfony

I have these entities
User
(ManyToMany)
Customer (OneToOne --> a customer can have a related customer)
My app works. Now I want to manage permissions.
When a user is logged in, I want to show only customers related to him and customers related to children customers.
For example,
Each time I use findAll(), it will find its customers.
Route /user/4/customer/7 : if customer 7 is not related to user, permission denied
I think I have to override Doctrine Repository or use EntityManagerDecorator
I'm just asking for what is the best practice to figure it ?
Thanks !

Basically, operation like searching a specific data should be delegated to the repository. Eventually if you have to search through different data source you could create a service for this specific responibility and inject there needed dependencies. In your case I would say you don't need to ovveride anything just create a UserRepository and write there a function which do what you need.
Check this out:
https://symfony.com/doc/3.3/doctrine/repository.html

Why don't you create your own custom findAll() function in your customerRepository That filters with your user ?
Something like
public function findAllRelatedToUser(User $user)
{
return $this->createQueryBuilder('c')
->innerJoin('c.user', 'u')
->andWhere('u.id = :user_id')
->setParameter('user_id', $user->getId())
->getQuery()
->getResult();
}

Finally I've found a better way.
Doctrine Filters
Listenning request on kernel, if my entity is concerned, apply my filter (adding WHERE id= xx)
I use this :
http://blog.michaelperrin.fr/2014/12/05/doctrine-filters/

Related

Related Entities and their Repository - selectively loading entities

I have a few entities that have a few associations. They are loading up fine now. So I basically have a customer entity, that relates to an address entity. I also have a receipt transaction that relates to a customer.
When I retrieve a small set of customers I want to retrieve their related receipts but that set is huge. I want just the receipts from the last 2 weeks.
I thought that I could use a custom repository then use a function like customer->getRecentReceipts() but that doesn't work since the customer entity doesn't know about the repository. And from reading on this forum, people seem to say not to use the repository this way. How should I structure things to limit the loading of my receipt entities. I'm trying to avoid loading all then sorting them with a php routine.
i think you forgot to link your repository at the entity.
Exemple:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
*/
class Product
{
//...
}
that's when you can use your repository functions
There's nothing wrong with making a repository method to do this. You don't have to deal directly with the customer entity to get its receipts.
I'd just create a simple method in ReceiptRepository like (Syntax might be not quite right, I don't have an IDE handy right now)
getReceiptsForCustomerByDate(Customer $customer, \DateTime $createdAfter=null) {
if (!$createdAfter instanceof \DateTime) {
$createdAfter = new \DateTime('now -2 weeks');
}
$qb = $this->getEntityManager()->createQueryBuilder();
return $qb->select('c, r')
->from('YourBundle:Customer', 'c')
->join('c.receipt', 'r')
->where($qb->expr()->eq('c', ':customer')
->andWhere($qb->expr()->gt('r.createdAt', ':createdAfter')
->setParameter('createdAfter', $createdAfter)
->setParameter('customer', $customer)
->getQuery()->getResult();
}
The above means your fetched customer entities will only have the relevant receipts. Because we haven't lazy loaded the receipts, $customer->getReceipts() will only return the receipts we have specified by date.
No, you can't call this from your Customer entity, but there's no reason you can't call it in a controller method. This is a perfectly valid way to get things done in Symfony.
You could easily modify this to get many customer receipts by passing an array of customers.

Symfony2 best way of removing business logic from controller and correct usage of model

I'm in searching of the best way of removing business logic from controller and correct usage of model(and maybe services).
Some details below.
Actually, my project is more complicated, but as example I will use Simple Blog application.
I have created my application (Simple Blog) in next steps:
created bundle
generated entities(Topic, Post, Comment)
generated controller for each entity, using doctrine:generate:crud
installed FOSUserBundle and generated User entity
So, I have all needed methods and forms in my controllers. But now I have some troubles:
Admin need to be able see all topics and posts, when simple User can only see
topic and posts where he is owner.
Currently there are indexAction, that return findAll common for any user. As solution, I can check in action, if ROLE_USER or ADMIN and return find result for each condition. But this variant keep some logic at action.
I also can generate action for each role, but what happened if roles amount will increase?
What is the best way to solve this problem with result for each role?
I need to edit some parameters before saving.
For example, I have some scheduler, where I create date in some steps, using features of DateTime.
Before saving I need to do some calculations with date.
I can do it in controller using service or simple $request->params edit.
What is the best way to edit some $request parameters before saving?
My questions I have marked with bold.
Thanks a lot for any help!
What I would do is to create a query which fetches the topics. Afterwards I would have a method argument which specifies if the query should select only the topics for a certain user or all topics. Something like this should do the work in your TopicRepository:
public function findTopics($userId = false)
{
$query = $this->createQueryBuilder('topic');
if($userId) {
$query->join('topic.user', 'user')
->where('user.id = :user_id')
->setParameter(':user_id', $userId)
;
}
return $query->getQuery()->getResult();
}
So, whenever you need to get the topics only by a user, you would pass a $userId to the method and it would return the results only for that user. In your controller you'd have something similar to this code (Symfony 2.6+):
$authorizationChecker = $this->get('security.authorization_checker');
if($authorizationChecker->isGranted('ROLE_ADMIN')){
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics();
} else {
$results = $this->get('doctrine.orm.entity_manager')->getRepository('TopicRepository')->findTopics($this->getUser()->getId());
}
You can try using Doctrine Events and create a PreUpdate depending on your case. See the documentation for more information. If you have a TopicFormType, you could also try the form events.
You are not supposed to "edit" a $request, which is why you can't directly do that. You can, however, retrieve a value, save it as a $variable and then do whatever you want with it. You can always create a new Request if you really need it. Could you be more specific what you want to do here and why is this necessary?

Doctrine custom repository methods and unmanaged entities

I've got custom entity repository (let's say CategoryRepository) that returns Doctrine entities. I also have newly created entity (let's say Product) that I want to persist.
Product is related to Category and, in that case, Product is the owning side of the relationship so I've got following code:
$category = $categoryRepository->customGetCategory($someCriteria);
$product = new Product();
$product->setCategory($category);
$em->persist($product);
and result is
[Doctrine\ORM\ORMInvalidArgumentException]
A new entity was found through the relationship
'Acme\SomethingBundle\Entity\Product#category' that was not configured
to cascade persist operations for entity: blahblah. To solve this
issue: Either explicitly call EntityManager#persist() on this unknown
entity or configure cascade persist this association in the mapping
for example #ManyToOne(..,cascade={"persist"})
For now I'm aware that all entities returned by custom repository methods using \Doctrine\ORM\Query::getResult() method where Query object is returned by EntityManager::createQuery($dql) factory method are detached by default. So I've got entity returned by repository that exists in database and I can't find a way for doctrine to have it managed just like any entity returned by f. ex. $repository->findBy() method.
Could anyone point me in right direction with this? I'd really like to solve that, it's killing me.
This is probably one of the top 5 Doctrine questions asked. Just difficult to search for. Could try searching on the error message.
The problem is that Category::setProduct is never being called. Update your Product entity with:
class Product
{
public function setCategory($category);
{
$this->category = $category;
$category->setProduct($this); // *** Add this
}
}

Symfony 2: filtering x-to-many relations

I have two entities that represent users (User) and friendship requests (FriendshipRequest). There is a oneToMany relationship between User and FriendshipRequest, so Doctrine creates a method that is called getFriendshipRequests() in the class User. This is ok, but FriendshipRequest has an attribute that is called status, so I would like that the User class could filter the friendship requests associated to it attending to their status. I have read Doctrine documentation, and I found out this:
Natively you can’t filter associations in 2.0 and 2.1. You should use
DQL queries to query for the filtered set of entities.
According to this, I suppose that I should create a FriendshipRequest repository and create a method called "findByStatusAndUser" (or something like that), but I think that's a crappy solution.
I would like to have a method in the User entity, like getPendingStatusRequests(). Is this possible? If it isn't, what would be the best solution?
As of Doctrine 2.3 you can use matching and Criteria.
Then you could use getPendingStatusRequests() in User entity just like you wanted.
For your example the code would look like this:
public function getPendingStatusRequests()
{
$criteria = Criteria::create(); //don't forget to use Doctrine\Common\Collections\Criteria;
$criteria->where(Criteria::expr()->eq('status', 1));
return $this->friendshipRequests->matching($criteria);
}
I think that "getPendingRequestsForUser($user)" method in the FriendshipRequest repository should be a good solution. Inside this method you just need to create an appropriate DQL.
This is a good solution, because all of the logic should be moved to repositories, leaving entities as small and clean as possible.
UPD: Also, you could use findBy method, as described here, ex:
$pendingRequests = $em->getRepository('MyBundle:FriendshipRequest')->findBy(
array('user' => $user->getId(), 'status' => 1)
);
But for me, first method is preferred.
You can certainly add getPendingStatusRequests() to user and then have it cycle through all the friendship requests and only return those with the appropriate status.
The only potential problem is that all of the friendship requests will always be loaded including those you don't need. It is up to you to decide if this is a real problem or not. It might be that once a friendship request is processed then it is removed so a user won't have many requests at any given time.
If you do want to avoid loading all the requests then make a query and use the WITH expression on your join clause. Something like:
$qb->leftJoin('user.friendshipRequests','request',
Expr\Join::WITH, $qb->expr()->eq('request.status', $qb->expr()->literal('Pending')));
And since you are using S2 I would not fool around with repositories. Just make a service called UserManager, inject the entity manager, and give it a method called loadUserWithPendingFriendshipRequests.

Sonata admin bundle, how to use entity repository classes

Using this code in PropertyAdmin extends Admin :
public function createQuery($context = 'list')
{
$user = $this->getConfigurationPool()->getContainer()->get('security.context')->getToken()->getUser();
$query = $this->getModelManager()->createQuery($this->getClass(), 'o');
$query->where('o.Creator=:creator')->setParameter("creator", $user);
return $query;
}
I was able to limit "list" results to those who "belong" to logged admin ie. only Properties (that is an entity) created by logged admin.
The problem:
By manually changing the URL (id value like 1, 2...), I can edit Property that belongs to other user. For edit action, above query is not called at all. How to change that behavior?
2.Instead of putting query in controllers, can I fetch it from PropertyRepository class? That would keep logic in models for which I could write unit tests.
3.I am trying:
ProductAdmin extends AdminHelper {....}
AdminHelper extends Admin { .... }
But it fails saying "Cannot import resource "D:_development\rent2\app/config." from "D:_development\rent2\app/config\routing.yml".
AdminHelper is abstract class but Sonata still reads it. Any solution?
1.a) Use ACL for your objects, CRUD controller has permission checking.
1.b) Redefine edit action, make sure that user tries to edit property that belongs to him, something similar to Page Admin Controller, there create action is redefined
2) In controller $this->getConfigurationPool()->getContainer()->get('doctrine')->getRepository($this->getClass()); gives you access to repository registered for this model. Probably there are few other ways to get service container and entity manager from it.
3) To create your admin class you should extend Sonata Admin: docs for this, this problem does not seems to be related to sonata as for me. Can you please provide content for D:_development\rent2\app/config\routing.yml ?

Resources