I've got a Symfony form field with a custom validator. If the user submits the form and validation fails, I'd like to correct the value and show it to the user for review.
How can I modify a submitted form field after validation?
PRE_SUBMIT isn't suitable as it's executed before the validation:
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
$data = $event->getData();
$data['myField'] = 'Modified!';
$event->setData($data);
});
I've also tried making the modification in the controller, but I get a You cannot change the data of a submitted form error.
if ($form->isSubmitted() && !$form->isValid()) {
$form->get('myField')->setData('Modified!');
}
Is there any way of doing this?
How is this way?
$myValue = '';
if ($form->isSubmitted() && !$form->isValid()) {
$myValue = 'Modified!';
}
return $this->render('my_template.html.twig', [
'form' => $form->createView(),
'myValue' => $myValue,
]);
In my_template.html.twig,
{{ form_widget(form.myField, {'value' : myValue}) }}
Related
I have two dropdowns in my form . Options in second dropdown depend on selected value in the first dropdown. If validation fails I need to get posted value of the first dropdown to set options in the second dropdown. So how to receive posted value in the form type class when validation fails?
$form = $this->createForm(MyFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/**
* Do your stuff when form is valid
*/
} else {
/**
* Here you can get the input data even failed with $form->getData()
*/
dump($form->getData());
}
$form->getData() gives me the values of input that didn't failed in the else:
array:4 [▼
"first_dropdown" => "hello"
]
$advert = new Advert();
$form = $this->createForm(AdvertType::class, $advert, ['method' => 'POST'])
->handleRequest($request);
if (false === $form->isSubmitted()) {
$form->submit([]);
}
if (false === $form->isValid()) {
return ['form' => $form];
}
$manager = $this->getDoctrine()->getManager();
$this->appendTags($advert);
$manager->persist($advert);
$manager->flush();
return $advert;
I needed something like below, in my form type class eg. DocType in buildForm() method I did:
$request = Request::createFromGlobals();
$myObjectId = $request->request->get('doc')['object']; // selected option of the first dropdown
$object = ($obectId) ? $objectRepository->find($objectId) : null;
Then I could use fetched object, to populate second dropdown, using query builder.
I'm having an issue with looping a form in Symfony using while loop. When the user enters one student period and it matches a registration reiterate the form to let them enter a 2nd student period and then a 3rd. I'm not doing it correctly or could I reiterate entity=new Student(); to let them enter two entities .
public function createAction(Request $request){
$entity = new Student();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$student = $em->getRepository('AcmeDemoBundle:Payrollperiod')
->findOneBy([
'begindate'=>$form->get('beginDate')->getData(),
'lastdate'=>$form->get('lastDate')->getData()
]);
$registration = $em->getRepository('AcmeDemoBundle:Payrollweek')
->findBystartdateAndenddate(
$form->get('beginDate')->getData(),
$form->get('lastDate')->getData()
);
$counter = count($registration);
while($counter<=2) {
if ($student){
$this->addFlash('error', 'Duplicate error: Student Period already existed.' );
return $this->redirect($this->generateUrl('student'));
}
elseif ($registration){
foreach ($registration as $reg) {
$reg->setStudentid($entity);
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('payrollperiod'));
}
else{
$this->addFlash('error', ' does not match .');
return $this->redirect($this->generateUrl('student'));
}
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
}
Read the documentation here http://symfony.com/doc/current/reference/forms/types/form.html#allow-extra-fields on adding fields. Your UI/Front-end should created more fields when the user needs to add a second or more periods. I should generate proper Symfony form elements and then when you post, you can handle it like a standard form post, iterating over the Request since the periods will be submitted as an array.
I did something like this in the past I adding a new form row was an AJAX call to a template that described the form input fields so I wasn't having to craft form HTML in Javascript/jQuery. .append() the template results to the form-- post and process the request.
I'm quite new here, be patient, please.
I'm trying to make notice board project in Symfony2 using FOSUserBundle.
I try to get logged user id to put it into form created with form builder (and then to MySQL database).
One of attempts is:
public function createNoticeAction(Request $request)
{
$notice = new Notice();
$form = $this->createFormBuilder($notice)
->add("content", "text")
->add("user_id","entity",
array("class"=>"FOS/UserBundle/FOSUserBundle:", "choice_label"=>"id"))
->add("isActive", "true")
->add("category", "entity",
array("class" => "AppBundle:Category", "choice_label" => "name"))
->add("save", "submit", array("label" => "Save"))
->getForm();
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
$em->persist($notice);
$em->flush();
return $this->redirectToRoute('app_user_showuserpage');
}
I tried many solutions again and again and I get some error.
You already have the user object Symfony > 2.1.x
In you Controller like this:
$userId = $this->getUser()->getId();
...
$notice->setUserId($userId);
$em->persist($notice);
Don't ->add field in you FormBuilder, its not safely. Set this value in you Controller and don't ->add this field in FormBuilder
for symfony 3.2.13
have excelent solution (just because is working, but is dangerous if someone discover it in pure HTML)
1) first build YourFormType class.
add normal field in Forms/YourFormType.php (if not, formbuilder tell you that you passing smth not quite right (too many fields) ; -) )
$builder
->add(
'MyModelAddedById',
HiddenType::class,
[
'label' => 'echhh', //somehow it has to be here
'attr' => ['style' => 'display:none'], //somehow it has to be here
]
);
2) in your controller
public function addSomethingAction(Request $request){
$form = $this->createForm(MyModelFormType::class);
//set field value
$request->request->set("prodModelAddedById", $this->getUser()->getId());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$product = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
$this->addFlash('success', 'record was added');
return $this->redirectToRoute('products');
}
return $this->render(
'default.add.form.html.twig',
[
'newprod' => $form->createView(),
]
);
}
explenation:
you are passing a field and variable to formbuilder (settig it already to default value!)
and important thing, becose of BUG in my opinion - you can't in your form type set method:
public function getBlockPrefix()
{
//return 'app_bundle_my_form_type';
}
because
$request->request->set
can't work properly if your POST data from form are in bag (parameterbag)
no entity managers, no services, no listeners...
hope it helps.
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
I have a form with one default value:
class GearType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options')
->add('model', 'choice', array('choices' => $this->getModelChoices(), 'data' => 2));
}
one of the requirements is form can be pre-populated by re-sellers by passing parameters in URL. It is also nice feature for potential customers to copy and paste link to email, communicators, etc.
I did it this way:
/**
* #Route("/car/gear")
* #Template()
*/
public function gearAction(Request $request)
{
$form = $this->createForm(new GearType());
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
return 'is valid';
}
} else {
$get = $this->getRequest()->query->all();
if (!empty($get)) {
$normalizer = new GetSetMethodNormalizer();
$form->setData($normalizer->denormalize($get, new Gear())); # look here
}
}
return array('form' => $form->createView());
}
unfortunately field 'options' has always default value, instead value passed as a parameter.
I have tried to change line # look here into
$gear = $normalizer->denormalize($get, new Gear());
$form = $this->createForm(new GearType(), $gear);
but no result.
It seems that solution is passing additional parameter to GearType object. I do not like this solution. Does anyone know better way?
Add this snippet, and modifiy between the [ ] as appropriate
$form->bind($request);
if ( [ passed parameters from querystring ] ){ //// New Code
$form->getData()->setOptions( [ processed parameter ]); //// New Code
} //// New Code
if ($form->isValid()) {
return 'is valid';
}
The reason for the field options always having default value may be the actual query. Instead of denormalizing and setting the data directly, modify else fragment to:
} else {
$form = $this->createForm(new GearType(), new Gear(), array(
'validation_groups' => array('not-validating')
));
$form->bind($request);
}
The form will validate only against validations associated with the not-validating group, which will avoid showing the common required alerts if the form is built form GET.
Docs about 'validations-groups': http://symfony.com/doc/current/book/forms.html#validation-groups
The question is similar to: Entity form field and validation in Symfony2?