Sonata + FOSUserBundle edit roles on edit form - symfony

I am using SonataAdminBundle for user administration. I would like to change roles on users. Currently my code in configureFormFields method is like this but roles are never updated and I don't know why.
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('roles', 'choice', array(
'choices' => array(
'ROLE_ADMIN' => 'ADMIN',
'ROLE_USER' => 'API USER',
),
'expanded' => false,
'multiple' => true,
'required' => false
))
->add('email')
->add('plainPassword', 'text', array('label' => 'Password', 'required' => false))
->end()
;
}

FOSUserBundle supports having multiple ROLES per user which is fine thing indeed. In my experience however, a common use case is a single role per user.
An easy way to manage this is to add the following method to your model/entity object to obtain a single role:
public function getRole() {
$role = $this->roles[0];
return $role;
}
Note: $role = $this->roles[0] will return the first ROLE in the database roles field. It may be that you need to choose the correct role with your own logic. Or, it may also be that you need to get the default role. If you use $this->getRoles() instead of $this->roles you have have the database roles plus the default role in the returned array.
Next you need to add a matching setter to allow you to save the single role per user. This implementation will work.
public function setRole($role) {
$this->setRoles(array($role));
}
Finally you will want to add a role field in your user form:
$builder->add('role', 'choice', array(
'choices' => array(
'ROLE_USER' => 'User',
'ROLE_ADMIN' => 'Admin',
'ROLE_SUPER_ADMIN' => 'Super Admin'
),
'multiple' => false
));
An important thing to note:
$builder->add('role'... : 'role' NOT 'roles'

If you will create labels exactly the same, as role - code will works just great. Something like
->add('roles', 'choice', array(
'choices' => array(
'ROLE_ADMIN' => 'ROLE_ADMIN',
'ROLE_USER' => 'ROLE_USER',
),
'expanded' => false,
'multiple' => true,
'required' => false
))
vendors version, which was tested with code above
doctrine/doctrine-bundle 1.8.1
friendsofsymfony/user-bundle 2.1.2
doctrine/orm 2.6.1
sonata-project/admin-bundle 3.33.0
sonata-project/doctrine-orm-admin-bundle 3.4.2
symfony/symfony 3.4.6

Related

Symfony Form - Collection Type with Checkboxes inside

I am using Symfony 5, I want to have a "User Edit" page in administration, in which I will change User Roles, I want to have checkboxes to define which role assign to user, so for that, I need Collection Type with CheckboxType entry inside (if I am true), but for first I can't use user roles array as value for collection type
$builder
->add('roles', CollectionType::class, [
'entry_type' => CheckboxType::class,
'entry_options' => [
'required' => false,
],
])
This throws error
Unable to transform value for property path "[0]": Expected a Boolean.
after that, I tried to use a model transformer to change the value, below is code how I did that
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function($rolesAsArray){
$rolesAsArray = array_flip($rolesAsArray);
foreach($rolesAsArray as &$role){
$role = true; // I also tried to set key instead of value - true
}
return $rolesAsArray;
},
function($rolesAsString){
dump($rolesAsString);die;
}
));
After this, I didn't get an error but I get the form with this look
So I haven't any option to change labels, and even I am submitting a form with these fields it throws an error
Expected argument of type "array", "null" given at property path "roles".
I found a way to do this with Select Box, but I can't found any way to do it with Checkbox.
If you have any ideas tell me, please.
You can use ChoiceType :
$builder->add('roles', ChoiceType::class, array(
'label' => 'form.label.role',
'choices' => User::ROLES,
'choice_translation_domain' => 'user',
'multiple' => true,
'expanded' => true,
'required' => true,
));
In User entity:
const ROLES = array(
'roles.admin' => 'ROLE_ADMIN',
'roles.secretary' => 'ROLE_SECRETARY',
'roles.user' => 'ROLE_USER'
);

Symfony 2.6 checked by default checkboxes

How I can make checkboxes checked by default based on data from database?
Now my form looks like:
...
->add(
"role", "entity", [
"class" => "AppDefaultBundle:OptionRole",
"required" => false,
"label" => "Roles for user: ",
"property" => "name",
"expanded" => true,
"multiple" => true
]
)
...
And I want to select defaults for this checkboxes based on data from other table.
You should probably add the choices property: http://symfony.com/doc/current/reference/forms/types/choice.html#choices
In your case you should have an array with all OptionRoles relevant for the (User ?) entity you are working on (the one you create the form for).
Assuming the doctrine User model knows it's OptionRoles (most likely a ManyToMany association) the form should automatically check the checkboxes of the Users OptionRoles.
Here is one example:
[
'label' => 'Select Modules',
'class' => 'Foo\BarBundle\Entity\Module',
'choices' => $this->availableModules(),
'property' => 'name',
'multiple' => true,
'expanded' => true
]
...
public function availableModules()
{
return $this->get('doctrine')
->getManager()
->getRepository('Foo\BarBundle\Entity\Module')
->findAll();
}

Use Sonata Field Type on normal Form Class

I'm trying to insert custom sonata form field type on the front page, not in SonataAdmin, something like this:
$form = $this->createFormBuilder($content)
->add('titleEs', 'text', array('required' => true, 'label' => 'label.title.spanish', 'attr' => array('class' => 'col-xs-12 form-control input-lg')))
->add('contentEs', 'ckeditor', array('required' => true,'label' => 'label.content.spanish', 'attr' => array('class' => 'col-xs-12')))
->add('titleEn', 'text', array('required' => true,'label' => 'label.title.english', 'attr' => array('class' => 'col-xs-12 form-control input-lg')))
->add('contentEn', 'ckeditor', array('required' => true, 'label' => 'label.content.english', 'attr' => array('class' => 'col-xs-12')))
->add('header', 'sonata_type_model', array('required' => true,'label' => 'label.content.headerImage'), array('link_parameters' => array('context' => 'content/front', 'size' => 'big')))
//->add('coverImage', 'sonata_type_model_list', array('required' => true,'label' => 'label.content.coverImage'), array('link_parameters' => array('context' => 'content/front', 'size' => 'small')))
//->add('sliderImage', 'sonata_type_model_list', array('required' => false,'label' => 'label.content.sliderImage'), array('link_parameters' => array('context' => 'content/slider', 'size' => 'normal')))
->getForm();
But when I execute that, it throws an error:
Catchable Fatal Error: Argument 1 passed to Sonata\AdminBundle\Form\ChoiceList\ModelChoiceList::__construct() must implement interface Sonata\AdminBundle\Model\ModelManagerInterface, null given
I can't understand why Symfony throws that error, if the Sonata Form Field Types are services.
Thanks to #gabtzi's answer I poked around in the source code of Sonata Admin and came up with a very similar solution. Assuming that we have two entities Movie and Genre with a many-to-many relation between them (Movie is the owning side), the solution in Symfony 4 and Sonata Admin 3.x would look like this:
<?php
namespace App\Form\Type;
use App\Entity\Movie;
use App\Entity\Genre;
use Symfony\Component\Form\AbstractType;
use Sonata\AdminBundle\Form\Type\ModelType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MovieType extends AbstractType
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// add fields
->add('genres', ModelType::class, [
'multiple' => true,
'class' => Genre::class,
'property' => 'name', // assuming Genre has property name
'model_manager' => $this->container->get('sonata.admin.manager.orm'),
'by_reference' => false
])
// add more fields
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Movie::class,
));
}
}
This is a very basic example, but should give an idea how to proceed further. Important things are:
you don't have to register the form type as a service if you use autowiring. Check that autowire is set to true in your config/services.yaml. Read the official documentation for more detailed information;
pass ContainerInterface to the constructor to get the container;
you don't use sonata_type_model anymore. You have to use ModelType::class. Pay attention to the use statements;
you can set mutiple to true for a M2M relation, otherwise it defaults to false;
you have to pass the entity class to class - in this case Movie::class;
you can specify property to use certain property of Genre. You don't have to declare this if you have defined __toString method in the entity class. Then the return value of this method will be used;
the most important thing: now that you have the container, get the service sonata.admin.manager.orm and pass it to model_manager. Without this everything falls in water.
I haven't however managed to display the button + Add new. It's worth mentioning that admin class for the related property must exist and be accessible (proper permissions set) - in this case GenreAdmin would be required, otherwise the button couldn't even theoretically work.
I just bumped into the same issue as you, but since it's the first hit I came across in Google, I'm just posting what I did to work around the issue.
I'm assuming you were dynamically creating the form in a controller. If not you would need to declare your class as a service and inject the sonata.admin.manager.orm service to it.
$form = $this->createFormBuilder()
->add('<name_of_field>', 'sonata_type_model', array(
'multiple' => true,
'class' => <className>::class,
'property' => '<propertyName>',
'model_manager' => $this->get('sonata.admin.manager.orm')
))
;
After that it rendered correctly for me as it would in admin context.
sonata_type_model need to know what kind of entity is related to your field.
If you define this in admin class, sonata use own internal method to check the relation.
So if you define it outside admin it is put null instead entity

Sonata Exception get too many admin registered

I get the message:
Unable to found a valid admin for the class: Aman\VarshneyBundle\Entity\ArticleTable, get too many admin registered: sonata.admin.appsreview,sonata.admin.review,sonata.admin.article
I am not able to figure out this issue.
you have to specify "admin_code" option in your field definition
in your admin class while building your form
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('user', 'entity', array(), array(
'admin_code' => 'your.user.admin.service'
));
}
It only happens when you have multiple admin classes for the same entity.
I will put code with the use for the 'configureListFields' method, if it's usefull for someone.
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('filename', null, array('admin_code' => 'your.file.admin.service', 'label' => 'File Name'))
->add('parent', 'sonata_type_list', array('admin_code' => 'your.file.admin.service', 'label' => 'Parent File'))
->add('_action', 'actions', array(
'label' => 'Actions',
'actions' => array(
'download' => array(
'template' => 'FileAdminBundle:File:list__action_download.html.twig'
)
)
));
}
As we see, if we have multiple fields, we must put the 'admin_code' in all of them, excepts the actions (if we have it).
Hope it helps.

symfony2 EWZRecaptchaBundle extra Fields

I have a problem with the EWZRecaptcha Bunlde (dev-master) and symfony 2.1.0.
The reCaptcha is displayed correctly and the image changes so i think the configuration is ok. But the reCaptcha is not validated and after submitting, $form->getErrorsAsString() says: This form should not contain extra fields.
Well, i think the extra fields are recaptcha_challenge_field and recaptcha_response_field that are sent from reCaptcha but i don think that i missed something in the docu so what can be wrong with them?
For validation i use the code from the docu: (i also tried the alternative, that was mentioned there)
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints as Recaptcha;
//...
/**
* #Recaptcha\True
*/
public $recaptcha;
//...
in config:
framework:
validation: { enable_annotations: true }
i added the field like this:
$builder->add('recaptcha', 'ewz_recaptcha', array(
'property_path' => false,
'attr' => array(
'options' => array(
'theme' => 'clean'
)
)
));
Maybe i forgot something essential, that was not mentioned in the docu?
Possibly try adding a 'constraints' option to the builder. My recaptcha builder add looks like this:
$builder->add('recaptcha', 'ewz_recaptcha', array(
'attr' => array(
'options' => array(
'theme' => 'red'
)
),
'label' => "Verification",
'property_path' => false,
'constraints' => array(
new True()
),
'help' => "Enter the words in the box for verification purposes."
));
So add a 'use' statement for the constraint:
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
and then add the constraint option:
'constraints' => array(
new True()
),
finally found the solution!
to get rid of the extra fields i added those two fields in my form class:
$builder->add('recaptcha_challenge_field', 'hidden', array('property_path' => false));
$builder->add('recaptcha_response_field', 'hidden', array('property_path' => false));
the validation then works with:
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
...
'constraints' => array(
new True()
)
the annotation doesn`t work for me:
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints AS Recaptcha;
...
/**
* #Recaptcha\True
*/
public $recaptcha;

Resources