Symfony2 Form Option Resolution - php

I'm using the FOSUserBundle and I'm trying to customize a user registration form with an is_admin option. I'm at a loss as to why the option value is not passed from the createForm() function call to the buildForm() function.
Below is the corresponding code in the controller:
$has_admin_rights = $this->get('security.authorization_checker')
->isGranted('ROLE_SUPER_ADMIN');
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.registration.form.factory');
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->createUser();
...
$form = $formFactory->createForm(
new RegistrationFormType(), $user, array('is_admin' => $has_admin_rights));
Below is the custom form class which should, in theory, resolve the is_admin option. However it seems like the option is not properly passed.
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if($options['is_admin'] === true)
{
$builder->add('uniqueRole', 'choice', array(
'choices' => array('ROLE_ADMIN' => 'user.role.admin',
'ROLE_TEACHER' => 'user.role.teacher',
'ROLE_STUDENT' => 'user.role.student'),
'label' => 'form.label.role',
'expanded' => false,
'multiple' => false,
'mapped' => false
));
$builder->remove('plainPassword')->add('plainPassword', 'hidden', array('data' => 'defaultPwd'));
} else {
// Default value is indeed set,
// and options[is_admin] always equals to false
if (array_key_exists("is_admin", $options)){
echo 'Set!';
} else {
echo 'Not Set!';
}
}
$builder->add('save', 'submit', array('label' => 'actions.add'));
}
protected function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array(
'is_admin'));
$resolver->setDefaults(array(
'is_admin' => false,
'data_class' => 'VMB\UserBundle\Entity\User',
'intention' => 'registration',
));
}
// BC for SF < 2.7
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
public function getParent()
{
return 'fos_user_registration';
}
public function getName()
{
return 'vmb_user_registration';
}
}
I've also tried passing the parameter to a constructor this way
$form = $formFactory->createForm(new RegistrationFormType($has_admin_rights), $user);
however the symfony2 appCache initializes the form object without the proper param value (which falls back to defined null default value).
What am I doing wrong?

I think it's just a matter of invoking the wrong service.
You obtained the instance of FormFactory through:
$formFactory = $this->get('fos_user.registration.form.factory');
but this is not Symfony's FormFactory. In the matter of fact if you look at it's source here, you will see this:
public function createForm()
{
return $this->formFactory->createNamed($this->name, $this->type, null, array('validation_groups' => $this->validationGroups));
}
Basically, createForm does not expect any arguments at all.
So, instead, you should use Symfony's own form.factory (source):
$has_admin_rights = $this->get('security.authorization_checker')->isGranted('ROLE_SUPER_ADMIN');
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('form.factory');
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->createUser();
$form = $formFactory
->createBuilder(new RegistrationFormType(), $user, array('is_admin' => $has_admin_rights))
->getForm();
Hope this helps...

Related

Symfony 5 - Notice: Array to string conversion in user create form because of roles [duplicate]

This question already has answers here:
Symfony form - Choicetype - Error "Array to string covnersion"
(3 answers)
Closed 1 year ago.
I'm having trouble solving this problem, when i get in the page for creating a user in my Symfony web app, the user controller complains about the createForm() function and the error only says "Notice: Array to string conversion".
The form is the default created with the crud, actually everything is almost default so I can´t think on a single reason this thing doesn´t work as intended.
I've tried to modify the form to use a multiple selection but that didn´t work either.
And I tried to modify the data type in the entity but that just didn´t even worked because it throwed an error in the file immediately.
Here is the UserType.php (form generator)
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('roles', ChoiceType::class, [
'label' => 'Tipo de usuario',
'attr' => [
'multiple' => true,
'class'=>'form-control myselect'
],
'choices' => [
'Admin' => 'ROLE_ADMIN',
'Usuario' => 'ROLE_USER'
],
])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
The user controller in the breackpoint where Symfony shows the error:
/**
* #Route("/new", name="user_new", methods={"GET","POST"})
*/
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user); // <-- This is the highlighted part.
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('user_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('user/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
And the user entity in the fragments where i declare the role variable and the getter and setter:
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
$roles is an array, you can see it in your configuration :
/**
* #ORM\Column(type="json")
*/
private $roles = [];
Even if we have type="json", it will be used as an array but in your database it will be a regular json.
ChoiceType here is a string so you can't use it in $roles.
You can add a data transformer (see similar response) to convert the value when displaying it in your select or when using it with your entity.
The simple way to do it is using a CallBackTransformer directly in your form type :
use Symfony\Component\Form\CallbackTransformer;
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
fn ($rolesAsArray) => count($rolesAsArray) ? $rolesAsArray[0]: null,
fn ($rolesAsString) => [$rolesAsString]
));
Or if your are not using PHP 7.4+
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesAsArray) {
return count($rolesAsArray) ? $rolesAsArray[0]: null;
},
function ($rolesAsString) {
return [$rolesAsString];
}
));

Symfony: How to test CollectionType that uses a FormType with a constructor

I have a form type StoreProfileType that makes use of a SocialProfileType to create a CollectionType to add as many social profiles as the user likes.
Both form types, SocialProfileType and StoreProfileType have a constructor that requires the EntityManager.
During tests, I can pass the EntityManager to StoreProfileType but I don't know how to pass it to the embedded form type SocialProfileType.
This is StoreProfileType:
class StoreProfileType extends AbstractType
{
/** #var ObjectManager */
private $manager;
/**
* #param EntityManagerInterface $manager
*/
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class)
->add('primaryEmail', EmailType::class)
->add('logoUploadingFile', FileType::class, ['required' => false])
->add('description', TextareaType::class, ['required' => false])
->add('address', AddressType::class, ['data_class' => AddressEmbeddable::class, 'empty_data' => new AddressEmbeddable([])])
->add('socialProfiles', CollectionType::class, [
'entry_type' => SocialProfileType::class,
'entry_options' => ['store' => $builder->getData()],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false
]);
// Transform the email string into a value object
$builder->get('primaryEmail')->addModelTransformer(new EmailTransformer($this->manager));
}
}
The FormType SocialProfileType is this:
class SocialProfileType extends AbstractType
{
/** #var EntityManagerInterface */
private $entityManager;
/**
* #param EntityManagerInterface $manager
*/
public function __construct(EntityManagerInterface $manager)
{
$this->entityManager = $manager;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('profileUrl', UrlType::class);
$builder->get('profileUrl')->addModelTransformer(new DomainEmbeddableTransformer($this->entityManager));
$builder->addModelTransformer(new SocialProfileTransformer($this->entityManager, $options));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SocialProfile::class,
'store' => Store::class,
'company' => Company::class
]);
}
}
I have set both forms in my services.yml to inject the EntityManager in the constructor:
form.type.store:
class: AppBundle\Form\Type\StoreProfileType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type}
form.type.social_profile:
class: AppBundle\Form\Type\SocialProfileType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type}
All works well when rendering the forms, but I don't know how to test the form.
This is the unit test method I'm using:
public function testSubmitValidData()
{
$formData = [
'name' => '',
'primaryEmail' => 'test#example.com',
'logoUploadingFile' => '',
'description' => '',
];
$mockStoreRepository = $this->getMockBuilder(EntityRepository::class)
->disableOriginalConstructor()
->getMock();
$mockStoreRepository->method('findOneBy')->willReturn($mockStoreRepository);
// Mock the FormType: entity
$mockEntityManager = $this->getMockBuilder(EntityManagerInterface::class)
->disableOriginalConstructor()
->getMock();
$mockEntityManager->method('getRepository')->willReturn($mockStoreRepository);
/** #var EntityManagerInterface $mockEntityManager */
$type = new StoreProfileType($mockEntityManager);
$form = $this->factory->create($type);
$form->submit($formData);
$this::assertTrue($form->isSynchronized());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this::assertArrayHasKey($key, $children);
}
}
As you can see, I can inject the EntityManager in the StoreFormType but I cannot inject it in the SocialProfileType as this is a job done directly by Symfony when rendering the form.
How can I pass the EntityManager to SocialProfileType when testing StoreProfileType?.
The error I receive is (obviously) this:
1) AppBundle\Tests\Unit\Form\Type\StoreTypeTest::testSubmitValidData
TypeError: Argument 1 passed to
AppBundle\Form\Type\SocialProfileType::__construct() must implement
interface Doctrine\ORM\EntityManagerInterface, none given, called in
/Users/Aerendir/Documents/JooServer/_Projects/Coommercio/Apps/app-trust-back-me-www/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php
on line 90
"... None given ...", and this is correct as I really dont' pass it... But... how can I pass it as it is embedded?

Symfony FormType PropertyAccessor.php Error

I am getting an error when saving data using a formType into a model that has a many to many relationship.
The error is
Catchable Fatal Error: Argument 1 passed to AppBundle\Entity\User::removeRole() must be an instance of AppBundle\Entity\Role, string given, called in /Code/project/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 616 and defined
What is the best way to inject the Role model into the form?
The model has the following:
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $roles;
/**
* Remove role
*
* #param Role $role
*/
public function removeRole(Role $role)
{
$this->roles->removeElement($role);
}
/**
* Add role
*
* #param Role $role
*
* #return User
*/
public function addRole(Role $role)
{
$this->roles[] = $role;
return $this;
}
There is the reverse in Role
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;
To save the data in the controller I have
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if( $form->isSubmitted() && $form->isValid() ){
$encryptedPassword = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($encryptedPassword);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('user_list'));
}
return $this->render('user/new.html.twig', array('form' => $form->createView()));
And the FormType has
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('email')
->add('username')
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'options' => array('attr' => array('class' => 'password-field')),
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
->add('roles', EntityType::class, array(
'class' => 'AppBundle:Role',
'choice_label' => 'name',
'multiple' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User'
));
}
}
I have worked out and it was a little gotcha that was the issue is and may cause others the same.
It is because:
class User implements UserInterface
which requires
/**
* #return mixed
*/
public function getRoles()
{
return ARRAY|STRING;
}
To get around this I create:
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $userRoles;
Everything then comes of getUserRoles and works fine.

Symfony checkbox for some entity

I have list developer, not all developer, developer who have some parameters by filter and I need create check box for this some developer and post developer in another action, in this action identification developer who have true checkbox, I try this but have null check develoepr
class SelectByIdentityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('id', 'entity', array(
'required' => false,
'class' => 'ArtelProfileBundle:Developer',
'property' => 'id',
'property_path' => '[id]', # in square brackets!
'multiple' => true,
'expanded' => true
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false
));
}
/**
* #return string
*/
public function getName()
{
return 'select';
}
}
and action where I visible my developers
/**
* Finds and displays a Project entity.
*
* #Route("/{id}/show", name="admin_project_show")
* #Method({"GET", "POST"})
* #Template()
* #ParamConverter("entity", class="ArtelProfileBundle:Project")
*/
public function showAction($entity, Request $request)
{
//some logic
$developers = $profileRepository->findBySkillsAndTag($developer, $skill_form);
$form = $this
->createForm(
new SelectByIdentityType(),
$developers
)
->createView()
return array(
'entity' => $entity,
'form' => $form,
'developers' => $developers,
);
}
and if I check developer click SEND and then in the controller action which receives the POSTed data:
/**
* Send email for Developers.
*
* #Route("/{id}/send", name="admin_project_send_email")
* #Method({"GET", "POST"})
* #Template()
* #ParamConverter("entity", class="ArtelProfileBundle:Project")
*/
public function sendAction($entity, Request $request)
{
$em = $this->getDoctrine()->getManager();
$developer = $request->getSession()->get('developer');
$form = $this
->createForm(new SelectByIdentityType())
;
$form->bind($request);
if ($form->isValid()) {
$data = $form->getData();//$data['id'] I have 0
$ids = array();
foreach ($data['id'] as $developer) {
$ids[] = $entity->getId();
}
$request->getSession()->set('admin/foo_list/batch', $ids);
}
Why in $data['id'] I have 0 ??
and my template
{% for entity in developers %}
<tr>
<td>{{ form_widget(form.id[entity.id]) }}</td>
Any ideas ??
You can filter the entities displayed in an entity field by using the query_builder option. That way you can just normally render the whole form and you get a checkbox list of only the entities that your query returns. If you need to display it in a special way, you can override the form templates.
Unfortunately, as far as I know you can't use DQL or repository methods in the query_builder option. You have to write the query-builder code directly into the form definition.
An example:
$builder->add('id', 'entity', array(
'required' => false,
'class' => 'ArtelProfileBundle:Developer',
'multiple' => true,
'expanded' => true
'query_builder' => function($repo)
{
return $repo->createQueryBuilder('d')
->where('x = y');
}
));

Creating works fine but Updating fails when using exactly same entity and form

Example below works fine when I try to create a new car in database. If I leave brands select box blank in the webform, I get "The Brand field is required" error after submitting which is perfectly fine and as expected.
Problem:
If I repeat exactly same steps like described above when trying to update a selected record, I should still get "The Brand field is required" error after submitting, instead I'm getting "ContextErrorException: Catchable Fatal Error: Argument 1 passed to Car\BrandBundle\Entity\Cars::setBrands() must be an instance of Car\BrandBundle\Entity\Brands, null given, called in .... line on public function setBrands(\Car\BrandBundle\Entity\Brands $brands)"
Any reason why or any solution?
CAR UPDATE CONTROLLER (DOESN'T WORK)
private function getForm($car, $id)
{
return $this->createForm(new CarsType(), $car,
array('action' => $this->generateUrl('cars_independent_update_process',
array('id' => $id))));
}
public function processAction(Request $request, $id)
{
$repo = $this->getDoctrine()->getRepository('CarBrandBundle:Cars');
$car = $repo->findOneBy(array('id' => $id));
if (! $car)
{
return new Response('There is no such car in database');
}
$form = $this->getForm($car, $id);
$form->handleRequest($request);
if ($form->isValid() !== true)
{
return $this->render('CarBrandBundle:Independent:cars_update.html.twig',
array('page' => 'Cars Update Independent', 'form' => $form->createView()));
}
exit('FINE');
}
CAR CREATE CONTROLLER (WORKS FINE)
class CarsCreateController extends Controller
{
private function getForm()
{
return $this->createForm(new CarsType(), new Cars(),
array('action' => $this->generateUrl('cars_independent_create_process')));
}
public function processAction(Request $request)
{
$form = $this->getForm();
$form->handleRequest($request);
if ($form->isValid() !== true)
{
return $this->render('CarBrandBundle:Independent:cars_update.html.twig',
array('page' => 'Cars Update Independent', 'form' => $form->createView()));
}
exit('FINE');
}
ENTITY
class Cars
{
/**
* #ORM\ManyToOne(targetEntity="Brands", inversedBy="cars")
* #ORM\JoinColumn(name="brands_id", referencedColumnName="id", nullable=false)
* #Assert\NotBlank(message="The Brand field is required.")
*/
protected $brands;
/**
* #param \Car\BrandBundle\Entity\Brands $brands
*/
public function setBrands(\Car\BrandBundle\Entity\Brands $brands)
{
$this->brands = $brands;
return $this;
}
/**
* #return \Car\BrandBundle\Entity\Brands
*/
public function getBrands()
{
return $this->brands;
}
}
FORM TYPE
class CarsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('brands', 'entity',
array(
'error_bubbling' => true,
'class' => 'CarBrandBundle:Brands',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'empty_value' => '',
'query_builder' => function (EntityRepository $repo)
{
return $repo->createQueryBuilder('b')
->orderBy('b.name', 'ASC');
}
))
->add('model', 'text', array('label' => 'Model', 'error_bubbling' => true))
->add('year', 'date', array('label' => 'Year', 'error_bubbling' => true))
->add('button', 'submit', array('label' => 'Submit'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Car\BrandBundle\Entity\Cars'));
}
public function getName()
{
return 'cars';
}
}
In your update version you don't transmit the id when you generate your route for the form action. Will be something like that :
private function getForm($car)
{
return $this->createForm(new CarsType(), $car,
array('action' => $this->generateUrl('cars_independent_update_process', array('id' => $car->getId()))));
}
Problem was related to Constraint so I added it to the class and it work fine now.
/**
* Class Cars
*
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="cars", uniqueConstraints={#ORM\UniqueConstraint(columns={"model", "brands_id"})})
* #UniqueEntity(fields={"model","brands"}, message="The Model and the Brand combination already exists in database.")
*/
class Cars
{
/**
* #var object
* #ORM\ManyToOne(targetEntity="Brands", inversedBy="cars")
* #ORM\JoinColumn(name="brands_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
* #Assert\NotBlank(message="The Brand field is required.")
*/
protected $brands;

Categories