Sonata admin bundle, how to use entity repository classes - symfony

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 ?

Related

how to make a dynamic roles in Symfony

I am a begginer, I would like to know if is possible to transform a roles in dynamic role like the #Route model.
/**
* #IsGranted("ROLE_{dance}_{level}")
* #Route("/{dance}/{level}", name="danceLevel")
*/
I have a project to make a website with some restrictions to accees to the content and I would like to throught by a interface like "easy Admin" to create a new category of dance and level that will be transform in role automaticaly
thanks by advance for your help
The security annotations would be for statically named roles and other permissions (checked by Voters).
You would be able to do more dynamic checks with:
$this->denyAccessUnlessGranted("ROLE_{$dance}_{$level}", $user, 'No access');
It's possible that a Security voter would be able to simplify the checks to what would be a static name and so used in #IsGranted (Or, it could for example, get the current $request, via the RequestStack service, to get dance and level parameters, if required).

Symfony - Acces only to their own customers

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/

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?

Create an Admin User with FosUserBundle

I try to create an Admin User with FOsUserBundle from command windows with the following command:
php app/console fos:user:create
In my project the Admin User extends other user with mandatory propriety. So, when I choose my username, mail and password, it tells me:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'latitude' cannot be null
How can I set the value "latitude" in my AdminUser? I also use PUGXMultiUserBundle.
Only possibile way to reach that to me is
1 - override the cli command of FOSUserBundle placed into Command/CreateUserCommand.php
2 - override the user create method of FOSUserBundle placed into Util/UserManipulator.php
// Command/CreateUserCommand.php
protected function execute(InputInterface $input, OutputInterface $output)
{
$username = $input->getArgument('username');
$email = $input->getArgument('email');
$password = $input->getArgument('password');
$inactive = $input->getOption('inactive');
$superadmin = $input->getOption('super-admin');
$latitude = $input->getOption('latitude'); //this will be your own logic add
$manipulator = $this->getContainer()->get('fos_user.util.user_manipulator');
$manipulator->create($username, $password, $email, $latitude, !$inactive, $superadmin);
$output->writeln(sprintf('Created user <comment>%s</comment>', $username));
}
and
// Util/UserManipulator.php
public function create($username, $password, $email, $latitude, $active, $superadmin)
{
$user = $this->userManager->createUser();
$user->setUsername($username);
$user->setEmail($email);
$user->setPlainPassword($password);
$user->setEnabled((Boolean) $active);
$user->setSuperAdmin((Boolean) $superadmin);
$user->setLatitude($latitude);
$this->userManager->updateUser($user);
return $user;
}
Of course when I say override i mean ... override :P So you haven't to modify FOSUserBundle original files (you know, it's dangerous for many reasons) but make your own files by making your bundle extended by FOSUserBundle
Are you wondering how to make your bundle extended by FOSUserBundle?
Into your bundle "mainfile" - is the one you use to register your bundle - just add this lines
public function getParent()
{
return 'FOSUserBundle';
}
Then you simply recreate the tree structure where your ovverride files lives into original bundle, into your custom bundle's Resources/ directory (same position, same file name, same annotations if any) and .... the magic can start :) (this is valid only for views, please pay attention!)
What "override" means?
Override means that you take an existent function, "shadow" it by redefining elsewhere (declare a function with the same name, no matter how many parameters it accept, no matter the type of paramenters since php doesn't support method overloading [except if you do some "hack"]) and then you can use it instead of the original one. This is a common technique for add extra functionalities to a function or to change the function itself.
Say that we have two classes, A and B with B that is a child class of A. Say also that A have a method called myMethod().
In B we can do something like
public function myMethod() {
parent::myMethod();
//add extra functionalities here
}
in that way we're adding extra functionalities as we're calling the parent ("original") method and then execute some extra functionalities
Whereas if in B we make something like
public function myMethod() {
//some code here, but not calling parent method
}
we're redefining the behaviour of myMethod()
How Symfony2 let me override methods?
As I said previously in my answer, you have to make your bundle a child of the bundle that containts the function(s) you're trying to override (in that case FOSUserBundle). Once you did it, use the Resources directory of your bundle to accomplish what you need. reproduce the "tree-folder-structure" of the original bundle (ie.: same names of the folders) until you reach the class that contains the function you need to override.
Follow your real example: you need to override execute() function contained in Command/CreateUserCommand.php. You have to create, into your bundle folder that path:
PathTo/YourCostumBundle/Command/
and place inside the file CreateUserCommand.php with the content I show you above.
If you don't understand where I find that path, please take a look to FOSUserBundle code and it will be absolutely clear!
Why is dangerous to modify the FOSUserBundle code directly?
Well, there's a lot of answer an critic point that I can show you. Choosing the main (not ordered for importance):
What if you need to update FOSUserBundle? You'll use composer and lost every modify that you made to FOSUserBundle code
What if you have more than one bundle into your project that need to use FOSUserBundle? Maybe the custom behaviour makes sense for a bundle but not for the other one. Costumizing the behaviour at local bundle level helps you to keep FOSUserBundle logic intact
What if you're developing a bundle that you want to share with other user? You need to force them to "take" your own costumized FOSUserBundle version and warn them about updating it
Finally: I perfeclty know that your entity isn't into FOSUserBundle, but I can bet that they extend FOSUserBundle base user so what I told above is applicable to your case.
Hope it's less fuzzy now :)
Documentation: http://symfony.com/doc/current/cookbook/bundles/inheritance.html#overriding-controllers
I always follow the pattern I learned in the symfony documentation itself:
php bin/console fos:user:create usertest mail#domain.com password
and sometimes I need change the "roles" on the table "fos_user"
then
a:0:{}
to
a:1:{i:0;s:10:"ROLE_ADMIN";}
After creating a user with:
bash-5.1# bin/console fos:user:create admin admin#mydomain.com password123
Promote the user with the ROLE_ADMIN role:
bash-5.1# bin/console fos:user:promote
Please choose a username:admin
Please choose a role:ROLE_ADMIN
Role "ROLE_ADMIN" has been added to user "admin". This change will not
apply until the user logs out and back in again.

sonata_type_model_list customize the admin class called

I am looking for the solution to customize the linked admin class when I use a sonata_type_model_list form type in my admin classes.
An example :
I have 2 admin for one entity named EntityA:
class EntityA
class EntityA1Admin
class EntityA2Admin
This entity is linked in many_to_one relationships with others entities : EntityB and EntityC.
In EntityBAdmin I want to call A1Admin on $formMapper->add('entityA','sonata_type_model_list');
In EntityCAdmin I want to call A2Admin on $formMapper->add('entityA','sonata_type_model_list');
Is there any solution to set manually the admin class that should be call by sonata_type_model_list ?
At least, if it's not possible, is there anyway to customize the default filters in the list view ? (is it possible to customize $dataGridValues through sonata_type_model_list field ?)
Thanks by advance (I already spend hours to find the solution in the code, but i can't find any clear solution....)
If I were you, I would go against using multiple admins for a single entity. I would first try to use some sort of context or parameters, to distinguish on what to show and what to not show in each case for the same EntityA admin (instead of using two separate admins for EntityA).
I believe you want to change filters that are shown in sonata_type_model_list. You might want to try this to know, whether you EntityA admin is being called from within sonata_type_model_list window:
protected function configureDatagridFilters(DatagridMapper $filterMapper)
{
$request = $this->getRequest();
if ($request->query->get('pcode') == '_entity_b_code_') {
...
}
}
Then accordingly add or not add needed filter fields depending on the context.

Resources