I have some form and if user isn't logged in yet i want to show some additional fields (like email, username) in this form and validate it.
Now i do it in that way:
Create new FormType:
class QuickRegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('username')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\User',
'validation_groups' => array(
'QuickRegister'
),
));
}
public function getName()
{
return 'quick_register';
}
}
In Controller:
$user = $this->getUser();
$formBuilder = $this->createFormBuilder($message, array(
'cascade_validation' => true
));
$formBuilder->add('body', 'textarea');
if (!$user) {
$formBuilder->add('quick_register', new QuickRegisterType(), array(
'property_path' => 'sender'
));
}
In template:
{% if form.quick_register is defined %}
{# render this fields #}
{% endif %}
And just after $form->bind($request); i get valid user in $message->getSender(); Everything is good! But i need to perform some other actions just after form validation:
if ($form->isValid()) {
$sender = $message->getSender();
if (!$sender->getId()) {
// perform some work with just registered user
// like sending email, generate passwords, so on
}
}
And this actions will be generally the same for such form from all project, so i want to optimize it.
At first i think i can create new service in DI container and just call it like
$this->container->get('my.user_service')->afterQuickRegister($sender);
But now i read about Form events in symfony2 on page http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html
I don't understand right now, can i use it for my purposes? Can i create such form subscriber in DI container? And generally, can i somehow change User just after creation? What is right form event for this? Thanks!
I implement it with FormEvents.
form.type.quick_register:
class: Acme\UserBundle\Form\Type\QuickRegisterType
arguments: [ #form.type.quick_register.subscriber ]
tags:
- { name: form.type, alias: quick_register }
And pass subscriber by form.type.quick_register.subscriber service. In subscriber i check form validation in POST_BIND event and do my stuff. Great!
Related
I'm trying since several days to find out how to ask to easyadmin to store the author_id (or user_id to be specific), but I can't find by myself.
Basicly, by using the createListQueryBuilder function in a specific controller to override the easyadmin controller, I found the way to display for each logged user, the articles they created.
Now I'm locked since several days as i dont know how to tell to easyadmin to store the currently logged author_id (or user_id),the solution is to get the author_id with $this->getUser()->getId() that's ok, but where or how can i link this with my author_id before persisting it to the database
For now if i use this code below the author can add manually its author_id but that's not safe:
{ property: 'user', type: 'entity', type_options: { class: 'AppBundle\Entity\User' ,multiple: false,expanded: true} }
I would like to use an input hidden or any other way that could be more safe
Thank you any anybody could help me
EDIT:it was the hell to find the solution but i finaly found the way to display ONLY THE CURRENT LOGGED USER(who can be a basic admin or contributor if you prefer) or THE FULL LIST OF REGISTERED USERS IN DATABASE if the current logged user as a specific role (in this case that's the website main admin)
here is the solution:
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\User;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\Route;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use AlterPHP\EasyAdminExtensionBundle\EasyAdminExtensionBundle;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
/**
* Article controller.
*
* #Route("articles")
*/
class ArticlesController extends BaseAdminController
{
/**
* #Route("/", name="easyadmin")
*/
public function indexAction(Request $request)
{
return parent::indexAction($request);
}
public function createListQueryBuilder($entityClass, $sortDirection, $sortField = null, $dqlFilter = null)
{
if(!$this->getUser()->hasRole('ROLE_DIRECTION_ADMIN'))
{
$response = parent::createListQueryBuilder('Articles', $sortDirection, $sortField, $dqlFilter); // TODO: Change the autogenerated stub
$response->where('entity.userId = :userId')->setParameter('userId', $this->getUser()->getId());
return $response;
}
$response = parent::createListQueryBuilder('Articles', $sortDirection, $sortField, $dqlFilter); // TODO: Change the autogenerated stub
return $response;
}
public function createEntityFormBuilder($entity, $view)
{
$formBuilder = parent::createEntityFormBuilder($entity, $view);
// Here I overwrite field to be disabled
if(!$this->getUser()->hasRole('ROLE_DIRECTION_ADMIN'))
{
$formBuilder->add('user', EntityType::class, array(
'class' => 'AppBundle:User',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.id = :id')->setParameter('id',$this->getUser()->getId());
},
));
return $formBuilder;
}
$formBuilder->add('user', EntityType::class, array(
'class' => 'AppBundle:User',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'asc');
},
));
return $formBuilder;
}
}
1/i created a custom ArticlesController and added 1 method to display the logged user article list if they have a low level role,and display all the website articles if the user role is high
2/ i created a second method which add the currently logged user_id field to the easyadmin formbuilder if the user has a low level role(a contributor for example) OR a full list of users in case the currently logged user is the website main admin
I would like to associate my class with the forms. Is it possible that I gave different names for forms than they are in the class? For example class have property username but I would like to be mapped in the form to input of id login
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('login', 'text', array (
'label' => 'Login'
)) //<- different name mapped by login
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
));
}
User entity:
class User {
protected username;
//...
}
Yes. You are looking for property_path. See http://symfony.com/doc/2.8/reference/forms/types/form.html#property-path
$builder->add('login', 'text', array(
'label' => 'Login',
'property_path' => 'username',
));
Yes you can. Add 'mapped' => false option to the field which you want to have different name than it is in entity. This tells symfony that when the form will be submitted, it would not try to map that field to entity. But here, you will need to do additional work - 1. add data from entities username property to the unmapped one, and 2. when submitting the form, map manually submitted login data to the username property. These steps can be done via Form Events, like this:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Set other fields...
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'onPreSetData']);
}
function onPreSetData(FormEvent $event)
{
$form = $event->getForm(); // The form
$data = $event->getData(); // It will be User entity
$form
->add('login', 'text', array (
'label' => 'Login',
'mapped' => false,
'data' => $data->getUsername(),
));
}
function onPostSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$data->setUsername($form->get('login')->getData());
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
));
}
I'm not sure why you want to do this, but I showed you one way of doing this. Another and simplier way like qooplmao commented would be using property_path.
I'm developing the Admin Panel for my website, and one part of it is managing users. I'm using FOSUserBundle for those tasks.
I'm using custom template (AdminLTE) for forms, and I cannot add a dropdown to select roles for user when I add a new one.
In UserType.php
$builder->add('roles', 'choice', array(
'label' => 'User Role',
'choices' => $this->roleHelper->getRolesForForm()
));
In WCB\SaleBundle\Helper\RoleHelper.php
...
public function getRolesForForm()
{
$roleList = self::flatArray($this->rolesHierarchy);
$roles = array();
foreach ($roleList as $roleId => $roleName) {
if ($roleId == 'ROLE_ADMIN') {
$roles[$roleName] = 'Admin';
}
if ($roleId == 'ROLE_USER') {
$roles[$roleName] = 'User';
}
}
return $roles;
}
...
Above getRolesForForm() function will return this array, which is correct format for using with Symfony's choice field type:
Array
(
[ROLE_ADMIN] => Admin
[ROLE_USER] => User
)
And the form's not working anymore, with this exception:
The value of type "array" cannot be converted to a valid array key.
When I add 'multiple' = true to form builder, it works, but it's not a dropdown. It's HTML select box which allow multiple selection.
$builder->add('roles', 'choice', array(
'label' => 'User Role',
'choices' => $this->roleHelper->getRolesForForm(),
'multiple' => true
));
I think, for role selection, it should be a dropdown, not a multiple-selection box. How can I achieve this? Anything wrong with my code? Thank you :)
I think it should be a multiple-selection box indeed.
Take into account that a user can (and usually will) have several roles. In your case, a user who has ROLE_ADMIN also has ROLE_USER (ROLE_ADMIN "includes" ROLE_USER if you are using FOSUserBundle).
Not really sure if this may be the problem since I don't have your full code and I am not sure what self::flatArray($this->rolesHierarchy) is returning but please notice that you are using $roleName, not $roleId as your array key. If $roleName is not a string then you will get this problem.
I faced same issue lately and this is my work around (I created DataTransformer):
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$builder->get('roles')->addModelTransformer(new RolesTransformer());
}
}
And DataTransformer class:
use AppBundle\Entity\User;
use Symfony\Component\Form\DataTransformerInterface;
class RolesTransformer implements DataTransformerInterface
{
public function transform($roles)
{
foreach ($roles as $role) {
if ($role !== User::ROLE_DEFAULT) {
return $role;
}
}
return $roles[0];
}
public function reverseTransform($string)
{
return [
$string, User::ROLE_DEFAULT
];
}
}
The role attribute from UserModel of FOSUserBundle expected an array. If you use a select normal, they don't return an array.
Trying to validate a choice field (multiple checkboxes) Im having this problem:
"Notice: Array to string conversion "
My validation file looks like this one:
Cgboard\AppBundle\Forms\UploadImageEntity:
properties:
image:
...
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
My form entity class (Im not going to save this to db for now):
class UploadImageEntity {
public $image;
public $cgnetworks;
}
And my form class:
class UploadImageForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', 'file')
->add('cgnetworks', 'choice', [
'choices' => $this->getCgNetworks(),
'multiple' => TRUE,
'expanded' => TRUE
]
);
}
public function getCgNetworks()
{
return [
'tumblr' => 'Tumblr',
'flickr' => 'Flickr'
];
}
}
Any idea?
perhaps you need to specify multiple in your validation
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
multiple: true
Check your Entity field getter. If you have something else instead of
public function getValue(){
return $this->value;
}
You can reach this error.
Form builder uses get and set entity methods, that's why you need to return an allowable value.
Okay, i'm trying to check if an user has a specific role, i did this
however, when i do this:
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nombre',null,array('label' => 'Usuario'))
->add('email')
->add('password', 'repeated', array(
'type' => 'password',
'invalid_message' => 'Los campos deben coincidir',
'first_name' => 'password',
'second_name' => 'confirmar password',
'options' => array('required' => false)
))
->add('cliente', 'entity', array(
'class' => 'ClientesBundle:Cliente',
'empty_value' => 'Company',
'required' => false,
'empty_data' => null)
**)**
$user = $this->securityContext->getToken()->getUser();
**if ($user->getRol() == 'ROLE_SUPER_ADMIN'){**
->add('rol')
**}**
;
}
tried this as well:
**if ($this->securityContext->getToken()->getUser()->getRol() === 'ROLE_SUPER_ADMIN'){**
->add('rol')
**}**
the bolded lines (the ones with **) have the tiny red line that indicates an error, and it's says unexpected if...
How do i fix this?
From controller you have to pass user object to form builder
$form = $this->createForm(
new YourType(),
$data,
array('user' => $this->getUser())
);
Then in form builder you can fetch it from $options:
public function buildForm(FormBuilder $builder, array $options)
{
$user = $options['user']
}
Don't forget to extend setDefaultOptions() with user index:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
...
'user' => null
));
}
If you declare your form type as a service, you can inject the token storage in your class.
So you declare the service in services.yml like this:
my_form:
class: AppBundle\Services\MyFormType
public: true
arguments: ['#security.token_storage']
And the form class like this:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class MyFormType extends AbstractType
{
protected $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->tokenStorage->getToken()->getUser();
// Use the user object to build the form
}
}
I know this is an old question, but I'd like to put forward a better alternative for checking roles inside a form type.
The issue
The issue with using the TokenInterface and the User object is that it does not check for inheritance. For example, consider the following security.yml:
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
If your user has ROLE_SUPER_ADMIN but not ROLE_ADMIN added to their roles, the above solutions will fail if you are using $user->hasRole('ROLE_ADMIN'), as the user does not explicitly have ROLE_ADMIN assigned to their user and hasRole() does not check hierarchy.
The solution
Use the AuthorizationCheckerInterface instead to gain access to the isGranted() function.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MyFormType extends AbstractType {
protected $auth;
public function __construct(AuthorizationCheckerInterface $auth) {
$this->auth = $auth;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
// ...
if($this->auth->isGranted('ROLE_ADMIN')) {
// Do the thing here
}
}
}
This will respect any hierarchy defined in security.yml. If we use the same yml file as above, $auth->isGranted('ROLE_ADMIN') will return true if a user has ROLE_SUPER_ADMIN but not ROLE_ADMIN assigned to their profile.
I succeed in doing this thing without pass to a service in symfony 3.4. I know my method is not the most "professionnal" but it is simple and it works.
First, send the user in your formType from your controller
$form = $this->get('form.factory')->create(addPlanExpectedType::class, $business,
array('user' => $this->getUser())
);
Secondly, recover the roles, and verify if "ROLE_AMIN" is in this $roles array
public function buildForm(FormBuilderInterface $builder, array $options)
{
$businessId = $options['data']->getId();
$user = $options['user'];
$roles = $user->getRoles();
$boolAdmin = in_array('ROLE_ADMIN', $roles);