Symfony2: dynamic generation of embedded form - php

Symfony2 has possibility of forms dynamic generation.
However there is big problem with dynamic generation of embedded forms based on user submitted data:
If I use FormEvents::PRE_SET_DATA then I can't receive post data for embedded form - only parent object data is available
$builder->get('contacts')->addEventListener(
FormEvents::POST_SET_DATA
function(FormEvent $event) {
$data = $event->getData(); //$data will contain embedded form object - not the data object!
}
);
If I use FormEvents::POST_SUBMIT then I may receive data but I can't modify form
$builder->get('contacts')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) {
$data = $event->getData(); //$data will contain filled data object - everything is ok
$form = $event->getForm(); //form will be ok
if ($data->getSomeValue()) {
$form->add(...); //Error: "You cannot add children to a submitted form"
}
}
);
Please help: is there any way to dynamically generate embedded form based on user submitted data?
I use Symfony 2.4.
Thank you very much in advance!

The problem was easy to solve: it is needed to use FormEvents::SUBMIT or FormEvents::PRE_SUBMIT events.
For both of them it is possible to get submit data and to change the form.
The difference between them:
FormEvents::PRE_SUBMIT - data is not normalized, so $event->getData()
returns simple array
FormEvents::SUBMIT - data is NORMALIZED, so $event->getData() returns
model object
And there is even better possibility:
You may use FormEvents::POST_SUBMIT BUT you need to attach it to the subform field - not to the whole subform.
In such case you'll be able to:
Get all POST data in normalized form (model object)
Modify form fields which goes after one to which event is bound
Fields values we'll be automatically set based on POST data

Related

Accessing all form fields during validation of Symfony2 form

Consider the following form:
$builder->add('home_team', 'choice', [$options])
->add('away_team', 'choice', [$more_options])
->add('timestamp', 'datetime_picker', [$usual_stuff]);
I wish to validate these fields, and see that no other Match exists with the same home_team, away_team and timestamp.
I have made a UniqueMatchValidator with a validate() function but I need some help here.
I'm going to do a database call with the values from the form to check for duplicates, but in order to do that, I need to know the values of all three fields, while applying the Validator to only one of the fields.
Question
How can I access the values of all the form fields from inside the Validator?
As mentioned above it's better to use FormTypes and data classes.
However even with arrays you can use form validation and get all fields using event listener:
$builder
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$event->getData(); //you will get array with field values
$event->getForm()->addError(...); // if something happens error can be addded
})
Actually form validator uses this event too.
Your form should basically contains an entity. You need to do your validations using callbacks in the entity. Using callback, you can access all the properties of that entity.
This link will surely help you to understand it better:
Symfony2 Form Validation Callback

Validate symfony form without post

I an using the symfony form to create a form and validate it. But i want to pre-validate the form on page loading itself ie. validate the form without post. Is it possible to do it in symfony?.
I have tried to use $form1->isValid(); on the else part of post. But its not working.
Also I tried to use the submit(),
$data = $form1->getData();
$form1->submit($data);
$form1->isValid();
but with no success
*the form fields are dynamic and the validations are also dynamic. So a form that is preloaded can have error fields.
What you can do is for sure manual Entity Validation. I can imagine cases when you can't trust data already saved in database or, you want to pre-fill object with data from other source (external request response) before giving it for user to edit.
Please read docs about validation here: http://symfony.com/doc/current/book/validation.html
Possible code:
$author = new Author();
// ... do something to the $author object
$validator = $this->get('validator');
$errors = $validator->validate($author);
In this case you don't use constraints from FormType but validator constraints (they can be declared in entity - they will be used also by FormType): http://symfony.com/doc/current/book/validation.html#constraints

How to bind Symfony2 form with an array (not a request)?

Let say I have an HTML form with bunch of fields. Some fields belong to Product, some to Order, some to Other. When the form is submitted I want to take that request and then create Symfony forms for Product, Order, and Other in controller. Then I want to take partial form data and bind it with appropriate forms. An example would something like this:
$productArray = array('name'=>$request->get('name'));
$pf = $this->createForm(new \MyBundle\Form\ProductType(), $product);
$pf->bind($productArray);
if($pf->isValid()) {
// submit product data
}
// Do same for Order (but use order data)
// Do same for Other (but use other data)
The thing is when I try to do it, I can't get $form->isValid() method working. It seems that bind() step fails. I have a suspicion that it might have to do with the form token, but I not sure how to fix it. Again, I build my own HTML form in a view (I did not use form_widget(), cause of all complications it would require to merge bunch of FormTypes into one somehow). I just want a very simple way to use basic HTML form together with Symfony form feature set.
Can anyone tell me is this even possible with Symfony and how do I go about doing it?
You need to disable CSRF token to manually bind data.
To do this you can pass the csrf_protection option when creating form object.
Like this:
$pf = $this->createForm(new \MyBundle\Form\ProductType(), $product, array(
'csrf_protection' => false
));
I feel like you might need a form that embed the other forms:
// Main form
$builder
->add('product', new ProductType)
->add('order', new OrderType);
and have an object that contains association to these other objects to which you bind to the request. Like so you just have to bind one object with the request and access embedded object via simple getters.
Am I clear enough?

How can I pass the current referenced object in form collection in Symfony 2?

I use this code
$builder->add('userTasks','collection',array('type' => new UserTaskType()));
This is working fine
userTasks will be a collection different userTask objects which will in turn create the form.
Now is there any way that i can pass that individual UserTask object in the constructor like this
$builder->add('userTasks','collection',array('type' => new UserTaskType($userTask)));
so that i can use that to generate Label for the form.
Is it possible
If i use $user->UseTasks then that will be a collection of all tasks but i only want that object whose form is being created
If I understand the question correctly, you want to adjust the form based on the actual data object which the form will be bound to? It is one of the those simple sounding requirements that is actually a bit involved.
You need to use the form event system:
http://symfony.com/doc/master/cookbook/form/dynamic_form_generation.html
It's not so bad once you have worked through it.
===
To answer the first comment:
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$data will be your object, in this case a $userTask.

Processing Zend_Form dynamically generated elements

I need to create a form where the elements (texbox, select, ..) will be dynamically inserted. Right now I have created a empty Form file with just a hidden element and them in my controller I go inserting elements according to certain conditions.
My form file:
class Form_Questions extends Zend_Form {
public function __construct() {
parent::__construct($options);
$this->setName('Questions');
// Hidden Label for error output
$hiddenlabel = new Zend_Form_Element_Hidden('hiddenlabel');
$hiddenlabel->addDecorator(new Form_Decorator_HiddenLabel());
$this->addElements( array($hiddenlabel) );
}
}
In the controller I have something like:
...
$form = new Form_Questions();
$request = $this->getRequest();
if ($request->isPost())
{
$formData = $request->getPost();
if ($form->isValid($request->getPost()))
{
die(var_dump($form->getValues()));
}
}
else
{
//... add textbox, checkbox, ...
// add final submit button
$btn_submit = new Zend_Form_Element_Submit('submit');
$btn_submit->setAttrib('id', 'submitbutton');
$form->addElement($btn_submit);
$this->view->form = $form;
}
The form displays fine but the validation is giving me big trouble. My var_dump() only shows the hidden element that is staticly defined in the Form file. It does not save the dinamic elements so altought I can get them reading what's coming via POST, I can not do something like
$form->getValue('question1');
It behaves like if Zend uses the Form file to store the values when the submit happend, but since the elements are created dinamically they do not persist (either their values) after the post so I can not process them using the standar getValue() way.
I would appreciate any ideas on how to make them "live" til after the post so I can read them as in a normal form.
The form which you are calling isValid() and getValues() methods on is actually your "empty" form - you have instantiated it only a few lines up and haven't added any elements to it at that point.
Remember that POST only sends an array of fieldName => fieldValue type, it doesn't actually send a Zend_Form object.
It is difficult to suggest a new solution without knowing what you are trying to achieve. It is generally better to add all possible elements to your Zend_Form right away, and then only use the ones you need in the view scripts, i.e. echo $this->form->myField;. This will allow isValid() to process all the elements of the form.
It sounds like the form is dynamic in the sense that the questions come from a db, not in then sense that the user modifies the form itself to add new questions.
Assuming this is the case, then I wouldn't add the question fields in the controller. Rather, I'd pass the questions to the form in the constructor and then add the question fields and the validators in the form's init() method. Then in the controller, just standard isPost() and isValid() processing after that.
Or, if you are saying that the questions to be added to the form are somehow a consequence of the hidden label posted, then perhaps you need two forms and two actions: one for the hidden field form and another for the questions.
Ok, the simplest solution I came up with - to my case and considering the really of the code I am currently playing with was to load all the questions I need from the database using a method from my Model (something like fetchQuestions()), them in my controller I go throught the recordset and create the form elements according to the current question of the recordset.
The elements are stacked in an array that is passed to my Form constructor. In the form constructor I read the array and generate all the dynamic elements. I them just echoed the form to the view.
I have not seem why it would be a bad idea to override the Form constructor as I also could not use any of the set/get methods to pass this to my form.

Categories