Symfony: How to get the current User Role in a Formtype or security.authorization_checker - symfony

I have a Formtype and I need to get access to the current Useres ROLE because I want to decide, which fields are beeing shown.
Is it possible to get access to the security.authorization_checker for example so that I can make an if clause:
if (!$this->get('security.authorization_checker')->isGranted('IS_ADMIN')) { ....

You can register your form as service and then pass security.authorization_checker as arguments, Check below sample code.
form.service.id:
class: YourFormClass
arguments: ['#security.authorization_checker']
tags:
- { name: form.type }
Then in your form class create __construct(AuthorizationChecker $authorizationChecker) method and then use AuthorizationChecker to check ROLE

As mentioned above you can create your onw form based on this snipper of code I used in order to render a field at any NON-admin user:
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Validator\Constraints\IsTrue as TrueConstraint;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class RegistrationType extends AbstractType
{
/**
* #var AuthorizationChecker
*/
private $authorizationChecker=null;
public function __construct(AuthorizationChecker $authorizationChecker)
{
$this->authorizationChecker=$authorizationChecker;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name',TextType::class,["label"=>"register.name","required"=>true,'translation_domain' => 'FOSUserBundle'])
->add('surname',TextType::class,["label"=>"register.surname","required"=>true,'translation_domain' => 'FOSUserBundle']);
if(!$this->authorizationChecker->isGranted('ROLE_ADMIN'))
{
$builder->add('accept_terms',CheckboxType::class,["label"=>"register.acceptTerms","required"=>true,'translation_domain' => 'FOSUserBundle',
'mapped' => false,'constraints' => new TrueConstraint(array('message' => 'Your Confirmation Message','groups' => 'Registration'))]);
}
}
// Extra stuff ommited for convenience
}
So as you can see I use if a user is admin via $this->authorizationChecker->isGranted('ROLE_ADMIN') piece of code.
SO you just have to put the '#security.authorization_checker' as service argument.

Related

How to access Sonata admin pool in form type

Use case
I' m trying to reuse sonata_type_model_list in the front admin of my website by defining this in my buildForm method of my Entity FormType :
[...]
->add('position', 'sonata_type_model_list', array(
'model_manager' => $categoryManager,
))
[...]
However I can't use
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category");
As I need to be in an AdminClass to use getConfigurationPool().
If anyone knows how to use getConfigurationPool() outside of an AdminClass or do you know how to declare sonata_type_model_list in order to use it outside of an admin class ?
You need to inject the admin pool in your form type.
Here is an example:
#services.yml
blast_base_entities.form.type.my:
class: Blast\BaseEntitiesBundle\Form\Type\MyformType
tags:
- { name: form.type, alias: blast_search_index_autocomplete }
arguments: [#sonata.admin.pool]
And in the form type:
use Sonata\AdminBundle\Admin\Pool
Class MyFormType
{
private $adminPool;
public function construct(Pool $adminPool)
{
$this->adminPool = $adminPool;
}
}
Then you can retrieve admins
$this->adminPool->getAdminByClass('Foo');
As writing in the doc, maybe you can use the sonata.admin.pool service.
$configurationPool = $this->container->get('sonata.admin.pool');
Solution
Thanks to #pbenard & #Mawcel for their answers, I combined them to achieve my goal as follow:
In my controller I create my form and inject $this->container->get('sonata.admin.pool') to it like so :
$form = $this->createForm(
new FormType($this->container->get('sonata.admin.pool')),
$entity,
$options
);
In my formType, I added the property $adminPool && the __construct method:
private $adminPool;
public function __construct(Pool $adminPool) {
$this->adminPool = $adminPool;
}
So I can now access the adminPool from the buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
[...]
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category" );
[...]
}

Sf2/3 Displaing different forms for Different roles(users)

I have an App that requires a complex access control. And the Voters is what I need to make decisions on Controller-level.
However, I need to build form for different users by different way.
Example: There are Admin(ROLE_ADMIN) and User(ROLE_USER). There is a Post that contains fields:
published
moderated
author
body
timestamps
Admin must be able to edit all fields of any Post.
User - only particular fields: published, body. (bay the way, only if this is an author of this post, but this is decided by voters).
Possible solution i found is dynamic form modification. But if we need more complexity, for example posts belongs to Blog, Blog belongs to author. And Post can be edited by direct author and author of the blog.
And Author of the Blog can also edit postedAt field, but it can't be done by direct author of the post.
I need to write some login in PRE_BIND listener.
Maybe there is some kind of common practice for that situation, or someone can show their own examples of.
You can do this creating a form type extension
Imagine a form type where you want to display a field only if ROLE_ADMIN is granted. For that you can simply add a new property to the field ('author' in this example)
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('published', 'text')
->add('moderated', 'text')
->add('author', 'text', [
'is_granted' => 'ROLE_ADMIN',
])
;
}
For this parameter to be interpreted, you must create a form type extension by injecting the SecurityContext Symfony to ensure the rights of the logged on user.
<?php
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContextInterface;
class SecurityTypeExtension extends AbstractTypeExtension
{
/**
* The security context
* #var SecurityContextInterface
*/
private $securityContext;
/**
* Object constructor
*/
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$grant = $options['is_granted'];
if (null === $grant || $this->securityContext->isGranted($grant)) {
return;
}
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
if ($form->isRoot()) {
return;
}
$form->getParent()->remove($form->getName());
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(array('is_granted' => null));
}
/**
* {#inheritdoc}
*/
public function getExtendedType()
{
return 'form';
}
}
Finally, you just have to save the extension as a service :
services:
yourbundle.security_type_extension:
class: YourProject\Bundle\ForumBundle\Form\Extension\SecurityTypeExtension
arguments:
- #security.context
tags:
- { name: form.type_extension, alias: form }
Dynamic form modification seems unnecessary. Once the user is logged in the roles should not change.
You could inject the security.authorization_checker service in your form type and use that in the buildForm method to conditionally add fields to your form. Depending on how much the forms differ, this might become messy with too many if-statements. In that case I would suggest writing different form types altogether (possibly extending a base form type for repeated things).

Symfony2 Container Aware Form Type

Is there a way to make a form type container aware?
As an example I have 3 entities Account, User and Event. Users have ManyToMany relationship in order to associate many users with many other users (called approvers), reason for this is so that an Event created by a User can have a list of Users who area able approve it. In the User edit form where I have an approvers multiple select field the list needs to be filtered by Account so I need my form type to be container aware in order to filter the list of available Users by Account ID.
Am I right in thinking that making the form type container aware is the right way to go? I'd like to use the entity manager to filter a list of Users by Account.
1 Inject the entity manager through the constructor
<?php
namespace Acme\YourBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
class YourType extends AbstractType
{
/**
* The entity manager
*
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//build your form here
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
}
public function getName()
{
return 'name_of_your_form';
}
}
2 Declare it as a service
services:
# Form type
acme_your_bundle.name_of_your_form.form.type:
class: Acme\YourBundle\Form\Type\YourType
arguments:
entityManager: "#doctrine.orm.entity_manager"
Note:
If you're starting with Symfony, take this advice:
look very closely at the code of the FOSMessageBundle, it will give you exactly what you need to do anything in symfony, from form models, to form factories, to the creation of special services (like composer, authorizer, etc..). The more you study this bundle, the quicker you will learn symfony, I guarantee you that 100%. Finally, in your specific case, look at the FormFactory in this bundle
This solution allows you to inject the container into many forms without each of your form types being a service:
Create a new form type:
class ContainerAwareType extends AbstractType implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'container' => $this->container
));
}
public function getName() {
return 'container_aware';
}
public function getParent() {
return 'form';
}
}
Declare as a service:
services:
app.container_aware_type:
class: Burgerfuel\CmsBundle\Form\Type\ContainerAwareType
calls:
- [setContainer, ['#service_container']]
tags:
- { name: form.type, alias: 'container_aware' }
This type is now available to be a 'parent' to any other form type - whether its a service or not. In this case the important part is that setDefaultOptions from this class will be used to help build the $options argument that will be passed into any 'child' form types.
In any of your form types you can do this:
class MyType extends AbstractType
{
public function getParent() {
return 'container_aware';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$container = $options['container'];
$builder->add( ...
This solution will be beneficial if you can't make your form type a service for some reason.
Or it can save you time if you are creating many types that require access to the container.
A simple way to do this, without doing any dependency injection / declaring a service.
In your FormType file, force the form to require an EntityManager
//..
use Doctrine\ORM\EntityManager;
class YourFormType extends AbstractType
{
//...
//...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
$resolver->setRequired('entity_manager');
$resolver->setAllowedTypes('entity_manager', EntityManager::class);
}
}
Then you'll be able to (and forced - important for testing), pass the entity manager from the controller.
public function yourControllerAction(Request $request)
{
//..
$em = $this->getDoctrine()->getManager();
$form = $this->createForm('Acme\YourBundle\Form\YourEntityType', $yourEntityObject, array(
'entity_manager'=>$em,
));
$form->handleRequest($request);
//..
}

Set the email manually with FOSUserBundle

I am using Symfony 2 + FOSUserBundle to manage my users.
The problem is that I would like to build the email address "manually" from the username, and so, do not require any field for the email.
I will create the email from the username because one requirement to sign up is to have an email from my university which follows a strict format (username#schoolname.fr).
I managed to override the RegistrationFormType to avoid the email field from being added to my sign up page but I still have the error "Please enter an email" when I submit the form.
How can I prevent the validation of the email address and how could I "build" it from the username?
Thanks!
(Sorry for the English, I know it's not perfect...)
There is a simple way around it. It is even mentioned in the official FOSUserBundle documentation. You just need to override the controller.
Create your own Bundle and extend the FOSUserBundle:
class CustomUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
And then override the RegistrationController:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
// here you can implement your own logic. something like this:
$user = new User();
$form = $this->container->get('form.factory')->create(new RegistrationType(), $user);
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$user->setEmail($user->getUsername() . '#' . $user->getSchoolname() . '.fr');
// and what not. Also don't forget to either activate the user or send an activation email
}
}
}
}
You should write event listener for fos_user.registration.initialize. From code docs:
/**
* The REGISTRATION_INITIALIZE event occurs when the registration process is initialized.
*
* This event allows you to modify the default values of the user before binding the form.
* The event listener method receives a FOS\UserBundle\Event\UserEvent instance.
*/
const REGISTRATION_INITIALIZE = 'fos_user.registration.initialize';
More info about event dispatcher: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
And example event listener: http://symfony.com/doc/current/cookbook/service_container/event_listener.html
UPDATE - how to code?
In your config.yml (or services.yml or other extension like xml, php) define service like this:
demo_bundle.listener.user_registration:
class: Acme\DemoBundle\EventListener\Registration
tags:
- { name: kernel.event_listener, event: fos_user.registration.initialize, method: overrideUserEmail }
Next, define listener class:
namespace Acme\DemoBundle\EventListener;
class Registration
{
protected function overrideUserEmail(UserEvent $args)
{
$request = $args->getRequest();
$formFields = $request->get('fos_user_registration_form');
// here you can define specific email, ex:
$email = $formFields['username'] . '#sth.com';
$formFields['email'] = $email;
$request->request->set('fos_user_registration_form', $formFields);
}
}
Notice: Of course you can validate this email via injecting #validator to the listener.
Now you should hide email field in registration form. YOu can do that by overriden register_content.html.twig or (in my oppinion better way) override FOS RegistrationFormType like this:
namespace Acme\DemoBundle\Form\Type;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationFormType extends BaseType
{
// some code like __construct(), getName() etc.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// some code for your form builder
->add('email', 'hidden', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
;
}
}
Now your application is ready for setting email manually.

Extending form types in Symfony2 (aka creating a new form widget)

I'm trying to create/expand a form type in Symfony2, what i want to make is a category selector like in the following image. For that i was reading in symfony2 doc, The chapter: "How to create custom field type"(http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html)
The table database for this this have the following aspect...
What i pretend it's in Symfony extend the hidden form type widget to create my own type, I can not find in the documentation of symfony how to access to the Entities data from the custom type, and also how to call to the custo type object methods in the twig file of the widget. (In the example the twig file is src/Acme/DemoBundle/Resources/views/Form/fields.html.twig )
I know that i have to do some ajax callings to auto load the subcategories every time somebody touch a category, i have done this in the controller, but firstly i want to know how to do what i wrote. Wish this widget be reusable for all :).
Thanks a lot guys!
You should declare your new type as service and inject the Entity Manager into it :
namespace Your\Bundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
class NewExampleType extends AbstractType
{
protected $em;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//your code
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
//your code
}
public function getParent()
{
return 'hidden';
}
public function getName()
{
return 'example_widget';
}
}
Then, declare new service in services.yml
services:
example.type:
class: Your\Bundle\Form\Type\NewExampleType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type, alias: "example_widget" }
Source: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service
Then, you should take a look here : http://symfony.com/doc/current/cookbook/form/data_transformers.html

Resources