Modify request before submit - php

I've got a problem to modify request on a form validation.
I have this entity :
class Ad {
private $id;
/**
* #var Entity\Category
* #ORM\ManyToMany(targetEntity="Entity\Category")
*
*/
protected $category;
}
For lot of case, I need many Category, so I put ManyToMany with checbox forms. But in one case, I need a form with only radio button on Category to get only one.
So I've created the corresponding FormType :
class AdFormType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('category', null, [
'class' => 'Entity\Category',
'required' => true,
'expanded' => true,
'multiple' => false,
'error_bubbling' => true
]);
}
}
The problem is when I do a $form->submit($request), it's failed because it wants ArrayCollection instead of Category entity.
So I've try to create a listener with PRE_SUBMIT Event to modify request, but it failed.
class AdListener implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return array(FormEvents::PRE_SUBMIT => 'onPreBind',FormEvents::POST_SUBMIT => 'onPostBind',FormEvents::SUBMIT => 'onBind');
}
public function onPreBind(FormEvent $event) {
$data = $event->getData();
$data['category'] = [$data['category']];
$event->setData($data);
}
}
Have you some ideas to force ArrayCollection of Category ?
Thanks for helping.
Bouffe

I think what you want to do here is use the collection field type. There's a great cookbook article on this here. That's probably the proper way to implement what you're trying to do anyway.

Related

Symfony Forms, formtype label based on a property value of the data object

Is it possible to set the label value of a form type to be a property value that is available in the data object?
My FormType class:
class ShapeFractionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('value', NumberType::class, [
'label' => 'name'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ShapeFraction::class,
]);
}
}
The ShapeFraction data class:
class ShapeFraction
{
public string $name;
public string $type;
public float $value;
// ...
}
Rendering the form:
$shapeFraction = new ShapeFraction('A', 'length', 10);
$form = $formFactory->create(ShapeFractionType::class, $shapeFraction);
// Render using Twig...
The result is a simple form containing a single input with 'name' as label. Is it possible to use the property value of ShapeFraction::$name as the label of the field? Result will become like this: A: <input..>.
I would like to achieve this without using the object data directly in Twig or something.
Thanks in advance!
You can access the data that you pass to your form using $options['data'].
That means you can do this:
/** #var ShapeFraction $shapeFraction */
$shapeFraction= $options['data'];
$builder->add('value', NumberType::class, [
'label' => $shapeFraction->name
]);

Symfony2 create a form type to add multiple records of the same type

I need to create a form type that will create multiple records of a certain Entity.
I figured out to create a parent Form Type and then add a collection inside. This parent Type isn't bind to any entity, but child Type is. The problem is, that when I process the request, I get an empty data object.
Here is my parent Type:
class ChallengeCollectionType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email_invitations', CollectionType::class, [
'entry_type' => EmailInvitationType::class
]);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false
));
}
}
My Child Type:
class EmailInvitationType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', EmailType::class);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Challenge::class,
'csrf_protection' => false
));
}
}
Here is how I process my data in Controller:
public function inviteEmailAction(Request $request){
$form = $this->createForm(ChallengeCollectionType::class);
$form->handleRequest($request);
dump($form->getData());
dump($form->get('email_invitations')->getData());
die();
}
I submit data in a POST request (this is API and there's no HTML form) like this:
challenge_collection[email_invitations][0][email] = "email#address.com"
challenge_collection[email_invitations][1][email] = "address#email.com"
And Controller returns empty [] for all dumps.
I've actually found a workaround, but it looks bad:
$data = ['email_invitations' => [new Challenge(), new Challenge()]];
$form = $this->createForm(ChallengeCollectionType::class, $data);
$form->handleRequest($request);
And this will work, so I have to send an array of empty objects of the type of the Entity to get request processed.
Or even this way:
$data = ['email_invitations' => []];
if(!empty($request->request->get('challenge_collection'))){
if(!empty($request->request->get('challenge_collection')['email_invitations'])){
for($i = 0; $i < count($request->request->get('challenge_collection')['email_invitations']); $i ++){
$data['email_invitations'][] = new Challenge();
}
}
}
There should be a better way to do it, please help me to find it.
You should try using allow_add and allow_delete options in collection field type. See the Collection field type documentation
Also, this is important for parent entity to have an mapped-by association with child entity, which would get you child objects in an ArrayCollection object in controller.
Further, if you want to save/remove them automatically as per the request, you need to implement a 'by_referencefalse and set association withcascade-persistandcascade-remove`.
I have used collection field type with API numerous times, and it worked like charm. Let me know if you need further explanation.
Maybe you should try in your controller something like :
$invitations = $this->getDoctrine()->getRepository('yourInvitationEntityClass')->findAll());
$form = $this->createForm(ChallengeCollectionType::class, $invitations );
You could find explanations here :
http://symfony.com/doc/current/cookbook/form/form_collections.html
One form with all row of one entity

Symfony access Entity Manager inside EventSubscriber

I have an Employer profile form and I am trying to link the State where Employer is located at with a City this part is working alright but the FormType got so long and this feature will be required at other places of the website so I decided to move this logic inside an EventSubscriber and reuse it where ever I need it.
Problem I am having is I am trying to wrap my head around how to inject EntityManager inside the EventSubscriber class.
I know I can add the following code inside my services.yml and that should do it buts its not working.
app.form.location:
class: AppBundle\Form\EventListener\AddStateFieldSubscriber
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: kernel.event_subscriber }
This is my EmployerProfileType where I am calling my addEventSubscriber which is AddStateFieldSubscriber()
class EmployerProfileType extends AbstractType
{
protected $em;
function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('companyName', TextType::class)
->add('companyProfile', TextareaType::class)
->add('companyLogo', FileType::class, array(
'data_class' => null
));
$builder->addEventSubscriber(new AddStateFieldSubscriber());
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\EmployerProfile',
));
}
public function getName()
{
return 'app_bundle_emp_profile_type';
}
}
This is my AddStateFieldSubscriber class where I need access to EntityManager
class AddStateFieldSubscriber implements EventSubscriberInterface
{
protected $em;
function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit'
);
}
protected function addElements(FormInterface $form, States $province = null)
{
// Remove the submit button, we will place this at the end of the form later
// Add the province element
$form->add('state', EntityType::class, array(
'data' => $province,
'placeholder' => 'provide_state',
'class' => 'AdminBundle\Entity\States',
'mapped' => false)
);
// Cities are empty, unless we actually supplied a province
$cities = array();
if ($province) {
// Fetch the cities from specified province
$repo = $this->em->getRepository('AdminBundle:Cities');
$cities = $repo->findByStates($province, array('name' => 'asc'));
}
// Add the city element
$form->add('city', EntityType::class, array(
'placeholder' => 'provide_state_first',
'class' => 'AdminBundle\Entity\Cities',
'choices' => $cities,
));
}
function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
// Note that the data is not yet hydrated into the entity.
$province = $this->em->getRepository('AdminBundle:States')->find($data['state']);
$this->addElements($form, $province);
}
function onPreSetData(FormEvent $event) {
$account = $event->getData();
$form = $event->getForm();
// We might have an empty account (when we insert a new account, for instance)
$province = $account->getCity() ? $account->getCity()->getStates() : null;
$this->addElements($form, $province);
}
}
The error I get is
Catchable Fatal Error: Argument 1 passed to
AppBundle\Form\EventListener\AddStateFieldSubscriber::__construct()
must be an instance of Doctrine\ORM\EntityManager, none given, called
in
/Users/shairyar/Sites/clickjobboard/src/AppBundle/Form/EmployerProfileType.php
on line 48 and defined
I am injecting EntityManager via service then why do I get this error?
If inside EmployerProfileType I replace
$builder->addEventSubscriber(new AddStateFieldSubscriber();
to
$builder->addEventSubscriber(new AddStateFieldSubscriber($this->em));
then things start working fine.
I thought in Symfony we are supposed to inject the dependencies by creating services? So how can I inject EntityManager inside my AddStateFieldSubscriber() class
What am i doing wrong? may be I am over thinking about it.
Will appreciate any feedback.
I think you need to tag your AddStateFieldSubscriber as kernel.event_subscriber.
Then the binding inside the form in not needed at all.
Instead you need to check for the correct form, and jump out of the event listener method if the form is not one of your forms using that subscriber, since the Event would be triggered on any form then, not only the EmployerProfileType form.

Adding choices dynamically using EventSubscriber to Symfony2 form

I am trying to set the choices for a form select dynamically since the choices come from a service call. However, when the form renders in the view, the choices are not there.
I'm doing the following in the FormType
<?php
namespace My\Form\Customer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ItemReturnRequestForm extends AbstractType
{
/**
* #var EventSubscriberInterface
*/
protected $reasonsSubscriber;
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'item_return_request';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('reason', 'choice', [
'label' => 'order.returns.reason_for_return',
'required' => true,
'multiple' => false,
'expanded' => false,
'placeholder' => 'order.returns.reasons.empty',
'empty_data' => null,
]);
$builder->addEventSubscriber($this->reasonsSubscriber);
}
/**
* #param EventSubscriberInterface $reasonsSubscriber
*/
public function setReasonsSubscriber(EventSubscriberInterface $reasonsSubscriber)
{
$this->reasonsSubscriber = $reasonsSubscriber;
}
}
The FormType has a service definition which injects the EventSubscriber instance since that is also a service definition with it's own dependencies.
The EventSubscrbier looks like
<?php
namespace My\Form\EventSubscriber;
use My\Customer\ItemReturnAware;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ReturnReasonEventSubscriber implements EventSubscriberInterface
{
use ItemReturnAware;
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'getReturnReasons',
];
}
public function getReturnReasons(FormEvent $event)
{
$form = $event->getForm();
if ($form->has('reason')) {
$options = $form->get('reason')->getConfig()->getOptions();
$options['choices'] = $this->itemReturnService->getReasons();
$form->add('reason', 'choice', $options);
}
}
}
Everything seems to work fine up until this point. Using XDEBUG I can see that the EventSubscriber is being triggered. The service call sets $option['choices'] to the expected array value & the field is added successfully.
However, when the form gets rendered. it's as if the EventSubscriber had never been called.
If it makes a difference, the options array is an un-ordered numeric list.
i.e.
$options = [
10 => 'First choice',
15 => 'Second choice',
20 => 'Third choice',
];
Any ideas?
This is an ancient question, but today I found it on the top results searching for event listener to modify form choices.
In my context I have an entity programmatically created, and I redirect the user to the editAction to finish filling the fields.
I have one choice that I can apply only in this particular case, I don't want to allow my user to use it outside it.
That's why I user the POST_SET_DATA event, because I already have an entity with populated fields.
This event listener is set in the formType, inside the
public function buildForm(FormBuilderInterface $builder, array $options)
{
Here a working solution for symfony 3.4:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
// get the form from the event
$form = $event->getForm();
if ('myParticularMode' == $form->get('mode')->getData()) {
// get the field options
$options = $form->get('mode')->getConfig()->getOptions();
// add the mode to the choices array
$options['choices']['MY_PARTICULAR_MODE'] = 'myParticularMode_display_name';
$form->add('mode', ChoiceType::class, $options);
}
});
If you want to replace the choices, you can remove this:
$options = $form->get('mode')->getConfig()->getOptions();
and set a new array for choices.

Option groups in symfony2 entity form type

Is there any way an entity field can be shown grouped in option groups in symfony2 (v.2.1), for example I have something like this in my form class:
$builder->add('account',
'entity',
array(
'class' => 'MyBundle\Entity\Account',
'query_builder' => function(EntityRepository $repo){
return $repo->findAllAccounts();
},
'required' => true,
'empty_value' => 'Choose_an_account',
);
But (of course) they are displayed as the repository class reads it from the db and I would like to display them grouped in the combobox. This post mentions that featured added to the 2.2 release out of the box, but what options do we 2.1 users have?
The Grouping would be based on a field called Type, lets say I have a getter for that called getType() in my Account entity that returns a string.
Thanks.
I did a similar thing when handling with categories.
First, when you build the form, pass the list of choices as a result from a function getAccountList() as follows:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('account', 'entity', array(
'class' => 'MyBundle\Entity\Account',
'choices' => $this->getAccountList(),
'required' => true,
'empty_value' => 'Choose_an_account',
));
}
The function should do something like follows (the content depend on the way you structure your result).
private function getAccountList(){
$repo = $this->em->getRepository('MyBundle\Entity\Account');
$list = array();
//Now you have to construct the <optgroup> labels. Suppose to have 3 groups
$list['group1'] = array();
$list['group2'] = array();
$list['group3'] = array();
$accountsFrom1 = $repo->findFromGroup('group1'); // retrieve your accounts in group1.
foreach($accountsFrom1 as $account){
$list[$name][$account->getName()] = $account;
}
//....etc
return $list;
}
Of course, you can do it more dynamics! Mine is just a brief example!
You also have to pass the EntityManager to your custom form class. So, define the constructor:
class MyAccountType extends AbstractType {
private $em;
public function __construct(\Doctrine\ORM\EntityManager $em){
$this->em = $em;
}
}
And pass the EntityManager when you initiate the MyAccountType object.

Categories