pre_set_data and post_submit form event - php

I have two lists in my form : "State" and "Country". State list depends Country list.
I handle this with form events and according to the doc I need two events : pre_set_data and post_submit. So here my code im my Form type :
$formModifier = function(FormInterface $form, Country $country) use ($options) {
// Query to get all states by countryID
}
1/ Initialisation
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($formModifier)
{
$country = ...;
$formModifier($event->getForm, $country);
}
2/ Post submit handler
$builder->get('country')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($formModifier) {
$formModifier($event->getForm()->getParent(), $event->getForm()->getData());
}
Everything works fine except in case of form submit pre_set_data is called and post_submit too. My query in $formModifier function is called twice.
Is it the normal behaviour ?

Sure it is normal. Both events occur, so both listeners are activated, calling your function $formModifier... so the function is called twice.

Related

Symfony2: init EntityType field with empty data

I have a field (EntityType => select in the view) in my FormBuilder that I want it to be in initialized with empty data so I can fill it after that in the view via ajax.
So I have read symfony's documentation about EntityType and I found the choices attribute that receives an array of data, so I gave it an empty one 'choices' => array() and it did the trick.
Now the problem is when I submit the form, symfony don't know anymore the type of the field and give me null.
This is the builder:
$buidler->add('supplier', EntityType::class, array(
'class' => 'SBC\TiersBundle\Entity\Supplier',
'attr' => array(
'class' => 'uk-select uk-select-supplier'
),
'choices' => array(),
))
As you can see the the type of the field is SBC\TiersBundle\Entity\Supplier but after submit symfony gives me null !
What should I do to achieve my goal?
All right, this is the solution:
First, I need to pass the EntityManager to my form, and to do this I have created a service:
services:
payment.supplier.form:
class: SBC\PaymentBundle\Form\PaymentSupplierType
tags:
- { name: form.type, alias: form_em }
arguments: ["#doctrine.orm.entity_manager"]
Then call the EntityManager in the __construct function:
private $em;
private $supplier;
function __construct(EntityManager $em)
{
$this->em = $em;
}
Second, I need to add two events to the form:
PRE_SUBMIT (to get supplier's code and create Supplier object using the EntityManager):
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function(FormEvent $event){
$data = $event->getData();
$code = $data['supplier'];
$this->supplier = $this->em->getRepository('TiersBundle:Supplier')->find($code);
}
);
And finally, use the POST_SUBMIT event to set the supplier object in the submitted data:
$builder->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event){
$object = $event->getData();
$object->setSupplier($this->supplier);
$event->setData($object);
}
);
Thanx to Виталий Бойко who gave me a hint about the form events.
So this is what I did with my knowledge and if you have a better solution please share it with us.
Symfony by default use security for forms, so if you didn't have choices in form builder, you can't pass custom choices to the form after render only via javascript, because you get not valid form. You need to create eventlistener for the form. Check this link for more informationenter link description here, here you can find how to add choices.
P.S. sorry for my English)

Disabling a form at PRE_SET_DATA symfony2

I'm working on a form extension that can disable some fields in form types when the underlying data is not new.I do this so I don't have to create seperate forms or event listeners for update/creation forms.i.e: I create the entity creation form and the extension disables the fields signified by an option if the underlying entity is not new.I bound an event listener to the PRE_SET_DATA event as such:
class RemoveFieldsExtension extends AbstractTypeExtension{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!$options['disable_on_update']) {
return;
}
$isNewCallback = $options['disable_on_update_is_new'];
$em = $this->em;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($em, $isNewCallback, $builder){
if(is_bool($isNewCallback))
return $isNewCallback;
if(is_callable($isNewCallback)){
$isNew = call_user_func($isNewCallback, $event->getData(), $em);
if(!$isNew){//the check for resolving if this is an update form or creation form
// $builder->setDisabled(true);
$form = $event->getForm();
$form->getConfig()->setDisabled(true);
}
}
},
-200
);
}
}
the extension works fine the problem is that the above code will produce 'FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'
it also didn't work when I tried $builder->setDisabled(true);.what can I do to accomplish this?

symfony2 dynamic submitted form modification

I am dynamically modifying form as shown in official example.
I got this field previous which is being submitted via jQuery auto complete feature as user types it. When match is set correctly form should auto update itself to add new field, according to "previous" field value.
$builder->get('previous')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($formModifier) {
$previous = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $previous);
});
$formModifier = function (FormInterface $form, $previous = null) {
if ($previous instanceof AwesomeEntity) {
$form->add('changes', new AwesomeChangesType(), array(
'data' => $previous->getLastComment('changes'),
'label' => false,
'mapped' => false
));
}
};
As you can see in line:
'data' => $previous->getLastComment('changes'),
The data is being set after submit has occurred, but it's not set, as far as I figured out: is that by default the symfony is preventing data modification after POST_SUBMIT event so the data is not set.
I don't know why, but it's probably for some good reason that this behavior is as it is. So the question would be is there any other way of setting data for needed field(s), in this situation without breaking the rules of symfony form logic?

Add a required form field based on submitted data in Symfony2

I have a form with a status select. If a certain status is selected and the form is submitted it should reload and require an additional field.
I have read Dynamic generation for submitted Forms and almost every other post on the internet and about this topic and tried different event combinations (and got different errors) but I still struggle to make this to work correctly.
This is what I have so far:
FormType
private function addProcessAfterField(FormInterface $form)
{
$form->add('processAfterDate', 'date', array('required' => true));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', 'entity', array(
'class' => 'Acme\Bundle\ApplicationBundle\Entity\LeadStatusCode',
'choices' => $this->allowedTypes
));
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$form = $event->getForm();
$data = $event->getData();
if ($data->getStatus()->getId() == LeadStatusCode::INTERESTED_LATER) {
$this->addProcessAfterField($form);
}
});
$builder->get('status')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event){
$data = $event->getData();
if ($data == LeadStatusCode::INTERESTED_LATER && !$event->getForm()->getParent()->getData()->getProcessAfterDate()) {
$this->addProcessAfterField($event->getForm()->getParent());
}
});
$builder->add('comment', 'textarea', array('mapped' => false));
$builder->add('Update', 'submit');
}
Error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Proxies\__CG__\Acme\Bundle\ApplicationBundle\Entity\Lead::setProcessAfterDate() must be an instance of DateTime, null given, called in /var/www/application.dev/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/application.dev/app/cache/dev/doctrine/orm/Proxies/__CG__AcmeBundleApplicationBundleEntityLead.php line 447
As already mentioned I tried different event combinations, one was almost working but then the date was never persisted to the entity so I added the \DateTime type-hint to the setProcessAfterDate() method. I am not sure if I don`t understand the event system correctly or if the error lies somewhere else.
Well, it might not be the best way to solve it, but to make long story short:
$form->handleRequest($request);
if($form->isValid()) // check if the basic version of the form is ok
{
$form = $this->createForm(new XXXXForm(), $form->getData()); // you recreate the form with the data that was submitted, so you rebuild the form with new data
if($form->isValid())
{
// ok
}
// not ok
}
Then inside buildForm function, you base the "required" attribute value of fields based on what you want:
'required' => $this->getCheckRequired($options)
private function getCheckRequired($options) // checks whether field should be required based on data bound to the form
{
if($options && isset($options['data'])
{
switch $options['data']->getStatus():
// whatever
;
}
return false;
}
As I said, this is not the best solution, and it doesn't fix your approach, but rather proposes a different one, but it does the job

Symfony form entity with allow add

I'm building an application with Symfony 2.3.
I have a Booking entity witch is related to a Customer entity by a ManyToOne relation.
In my form, i would like to be able to select one existing customer or create a new one.
For exemple by having a "new customer" option in my customer select who will display the customer form with javascript for exemple.
In fact i'm trying to build an Entity form field with an "allow_add" option like in the collection form field.
Any idea of how i can do that ?
Thank you very much.
Thanks for contributing. I found a way to achieve it !
The solution is to have one field with the Customer form, it has to be mapped, and an entity field en the Customer entity but not mapped.
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($company) {
$form = $event->getForm();
$data = $event->getData();
$form->add('customer_list', 'entity',
[
'class' => 'SomeBunlde\Entity\Customer',
'label' => 'Client',
'property' => 'fullName',
'expanded' => false,
'multiple' => false,
'mapped' => false,
'query_builder' => function(EntityRepository $er) use ($company)
{
return $er->getByCompanyQueryBuilder($company);
},
]
)
;
if ($data->getCustomer() === null) {
$form->add('customer', new CustomerType());
}
}
After i add an extra option to the Entity form field overloading the finishView method :
public function finishView(FormView $view, FormInterface $form, array $options)
{
array_unshift($view->children['customer_list']->vars['choices'], new SfFormExt\ChoiceView('test', 'new', 'Nouveau client'));
}
Then i add two event listeners, a pre_submit to delete the mapped embeded customer form and its data :
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function(FormEvent $event) use ($em) {
$data = $event->getData();
if ($data['customer_list'] !== 'new') {
unset($data['customer']);
$event->getForm()->remove('customer');
// setting data w/out customer to avoid extra-field error
$event->setData($data);
}
}
);
and a bind event to attach the existing customer to the booking :
$builder->addEventListener(
FormEvents::BIND,
function(FormEvent $event) use ($em) {
$form = $event->getForm();
$data = $event->getData();
if (!$form->has('customer')) {
$existing_customer = $form->get('customer_list')->getData();
if ($existing_customer instanceof Customer) {
$data->setCustomer($existing_customer);
}
}
}
);
I know it may not be state of the art code but it works pretty well.
Edit : I had an issue with this technique because when the customer_list is set to new, it throws an error. I didn't find a way to avoid this error (If you have any idea of how i can achieve this !) so i decided to modify the pre_submit to set to '' the data of customer_list value if we are in the new customer case, then i detect, in the controller, if there is a form validation error on the new client form in order to correctly display it.
I think the best way to do that is managing this workflow with javascript.
If you user choose to create a new customer, you open a create new customer form in a modal and via Ajax create the new customer. The response of the create action returns the id in the response which will be used by you to create the booking with the newly created customer.
The trick is: you will always create a booking from an existing customer. Your user can create a new customer in the process, but in fact it'll be created before you create the booking record.

Categories