i have two embeded forms Form1 and Form2 in a general form class FormA,
in the general class, besides the embeded forms i have a choice widget and in the doBind i try to unset one of the embeded forms (depending on the choice widget content)
this does'nt WORK :
Person and animal models inherit from creature(column agregation)
protected function doBind(array $values)
{
//embeded forms are 'person' and 'animal'
$forms = $this->embeddedForms;
if($values['type']== 'animal')
{
unset($forms['person']);
unset($values['person']['last_name'], $values['person']['first_name'] , $values['person']['civility'], $values['person']['id'] );
}
parent::doBind($values);
}
the form is never unseted and in the db i have 3 saves (one for animal, one for the person and one for the creature)
any idea guys???
Unsetting an embedded form after the configure() method is called is too late in the lifecycle of a form.
Implement your configure() method so it only embeds those subforms you realy need.
For example pass the value of the selected choice to the form and then decide in the configure() method whether to add the PersonForm or the AnimalForm.
Related
Issue: multiple fieldsets in form are not populated/hydrated when using $form->bind($object). How do you populate 2 different fieldsets in a form vai 2 different entity objects?
I have 2 fieldsets: FieldsetA, FieldsetB
A form RegisterFrom calls these in its init() method
class RegisterForm extends Form
{
public function init(){
$this->add(array(
'name' => 'service_provider_fieldset',
'type' => ServiceProviderFieldset::class, // this is one model/entity
));
$this->add(array(
'name' => 'location_fieldset',
'type' => LocationFieldset::class, // this is a separate model/entity
));
}
}
Creating the fieldsets: (note commented out attempts at hydration)
class ServiceProviderFieldset extends Fieldset
{
public function init(){
//parent::__construct($name);
/*
$this
->setHydrator(new ClassMethodsHydrator(false))
->setObject(new ServiceProvider())
;
*/
/*
$this
->setHydrator(new ReflectionHydrator(false))
->setObject(new ServiceProvider())
;
*/
$this->add(array(
'type'=>'Hidden',
'name'=>'id',
'options'=>array(
'label' => 'Service Provider Id'
),
'attributes'=>array(
'id'=>'providerId'
)
));
}
}
Controller:
$provider = $this->findServiceProviderById($providerId); // this is set from DB call and correctly creates a Provider() object with populated values.
$location = $this->findServiceProviderLocationById($locId);
$form = $formManager->get(RegisterForm::class);
$form->bind($provider);
$form->bin($location);
// $form->get('service_provider_fieldset')->bindValues(...);
View:
$formElement = $form->get('service_provider_fieldset')->get('email');
etc...
The form renders in the view correctly BUT without the populated data.
NOTE: NOT using Doctrine but I retrieve the data from the DB OK.
NOTE: IF I set this flag 'use_as_base_fieldset' => true, then 1 of the Objects (ServiceProvider) populates, visa-versa if I set the location fields to 'true' then that populates.
I've been searching for a couple of hours, trial and error with no success and I'm hoping it's just my fatigue which has missed a simple setup/config step to get this to work.
Summary: How do you populate 2 or more Fieldsets with 2 or more entities within a form?
Bind(), fieldset->bindValues()?,
Tried:
$form->get('service_provider_fieldset')->allowObjectBinding(true);
$form->get('service_provider_fieldset')->allowedObjectBindingClass(\Provider\Form\ServiceProviderFieldset::class);
These are some links that are close but still cannot populate both field sets via separate entities.
ZF2 Form Hydration with multiple objects and fieldsets
https://framework.zend.com/manual/2.4/en/modules/zend.form.collections.html
hydrating multiple objects from fieldsets ZF2
The collections (product/brand/category) example implies a 'single' collection using the 'use_as_base_fieldset' => true, is used to bind()...?
On your webpage, check the form's elements names that relate to your fieldsets. They should be something like this: yourFieldsetName[yourElementName]. If you just see yourElementName, that most likely means that forgot to prepare() your form in the view script.
This is exactly what happened to me, and after I prepare()ed the form, all the objects got hydrated without a problem.
UPDATE = answer to comment's questions: Not resolved as such. Is this bad design? Note: I am using prepare() on in the view.
If everything works fine, your 2 objects should hydrate. use_as_base_fieldset flag is used for basically saying, 'hey that's me (the fieldset) you should only hydrate object with data/extract data from object'. So what you get with one object being hydrated and the other not, and vice versa is predictable. It's quite difficult to say what's going wrong without looking at your complete code. I'm afraid posting too much will also take time for the answerer's to grasp, and my experience is that such questions are usually left unanswered. What I usually do in situations like yours is that I go step by step in the Zend Form's and Fieldsets methods used in hydration/extraction. I use \Zend\Debug\Debug::dump($somethingThatYouWantToCheck); die();. That's not the best method I presume, but it works.
If I were you, I would also do the following.
From your post, it's not clear why you use form's init() method. The init() method is used when you want, for example, some elements in your form be filled from DB (like <select>). The Form runs init() method when some things that aren't available in in the __construct() method yet, but only after the instance of the form is created (not 100% sure about this, double-check this).
Don't worry about good/bad design. Design is a very good thing, but if you have a small or middle system, the design considerations won't affect the performance/complexity of the system. But rather you'll spend really a lot of time doing everything right than just doing it and if it works ok, forgetting about it.
If you don't wanna go with \Zend\Debug\Debug::dump($somethingThatYouWantToCheck); die(); (that could be quite tedious, I know), create one fieldset and attach to it your desired 2 fieldsets. Then include this fieldset in the form and use use_as_base_fieldset = true on this fieldset (of course you'll also need to create object corresponding to this fieldset with 2 nested objects that are attached to your current fieldsets, and attach the object to the fieldset).
Hope this helps at least a little.
To work with several objects you need:
1. Create a form with fieldset for each object as you have done.
You have to specify a name for each fieldset (e.g. in constructor).
2. In each fieldset we need to specify hydrator
e.g.: $this->setHydrator(new ClassMethods());
Zend\Hydrator\ClassMethods for using getter functions or
Zend\Hydrator\ArraySerializable for using getArrayCopy method.
and allow object class:
$this->setAllowedObjectBindingClass(YourClassObject::class);
You can do it in init method in fieldset.
3. Set hydrator for main form:
$this->setHydrator(new ArraySerializable());
4. Now in controller method you can create object of Zend\Stdlib\ArrayObject:
$obj = new ArrayObject();
then add your objects with a key equals fieldset name:
$obj->offsetSet("fieldset_name", $your_object);
and then you can bind $obj to your form:
$form->bind($obj);
I hope this helps. And don't forget about prepare method:
return new ViewModel(["form" => $form->prepare()]);
In Symfony forms there are two types involved in handling radio buttons: ChoiceType and RadioType.
ChoiceType is building the structure of the form by adding RadioType buttons as sub forms.
I wanted to add a different image beside each Radio button. To achieve this I changed the Form Theme of choice_widget and radio_widget.
It is working properly.
But now I want to be able to use radio buttons as well as my 'modified_radio_buttons'.
This is pretty difficult since RadioType is added along in the ChoiceType class by adding sub forms of 'radio' type.
class ChoiceType extends AbstractType
{
...
private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options)
{
foreach ($choiceViews as $i => $choiceView) {
...
// =============================================================
// I would like to change this below to have 'my_radio' instead
// =============================================================
$choiceType = 'radio';
...
}
$builder->add($i, $choiceType, $choiceOpts);
One solution could be to create my own MyChoiceType, copying whole code from ChoiceType and just modifying it to add extra parameter and add 'my_radio' sub forms instead of 'radio' sub forms in the addSubForms method.
This works but it's not very clean in my opinion.
Would you have a suggestion to handle this?
I'm making a page for adverts. An advert can be of different types and therefore have different data. For instance, a vehicle would have the make and the model as extra data.
Right now, I've got one base doctrine entity Advert which contains the data that every advert requires. Different adverts in turn innherits this data (doctrine2 discriminatormap)
I need to populate the form dynamically (with ajax and symfony2 forms) if the user choose to create a vehicle ad I want to display the options for a vehicle advert. But I also need to change the entity to be of the form AdvertVehicle.
Is this possible? I did read the cookbook entry at the symfony2 homepage
"How to Dynamically Modify Forms Using Form Events":
This should be handled by making an AJAX call back to your application. In that controller, you can submit your form, but instead of processing it, simply use the submitted form to render the updated fields. The response from the AJAX call can then be used to update the view.
I understand how to make an ajax call back to my controller, and i understand how to use the form-events but how do I get the response of a rendered select-box (containing vehicle models for instance) back? With a new AbstractType? or formbuilder?
And then when the user actually submits the form I need to use the entity of the selected advert type. Can I change the entity according to the users choice in the form dynamically?
Edit
I checked the form innheritance out that's great, thank you. I extend the AdvertType and override the buildForm() method and before I add the items I need for the AdvertVehicleType I call the parent method.
Futher Explanation
Every advert entity contains price, description, title and category. Some adverts contains more, such as make and model. They are differentiated by the discriminatormap (doctrine2)
Example:
// -- Entity
class CarAdvert extends Advert {
protected $model;
protected $make;
}
// -- Entity
// -- This uses discriminator mapping
class Advert {
protected $title;
protected $description;
protected $price;
protected $category;
}
if the user selects the category cars I want to use the CarAdvert entity (for validation and persistance) if the user selects the house hold itemcategory I just want to use the normal Advert entity.
One major problem is still that I cannot figure out how to render the extended form via ajax. Any tips on this part? When the user selects car as a category, I want the form to be updated (via jQuery/ajax) but how do I make a controller that retrieves just the extended part of the form and sends the html back as a response (without using twig and rendering it in a view, is this possible)?
Solution:
See answer below!
Solution:
The solution to my problem was to create a few extra functions in the controller to solve the issue where I want to be able to change the entity and form "on the fly" from a selection by the user..
public function indexAction(Request $request)
{
$form = $this->getForm($request);
$form->handleRequest($request);
return array(
'form' => $form->createView(),
'request' => $request->request,
);
}
Where getForm retrieves the form, (e.g AdvertVehicleType for vehicles or AdvertType for a "default" advert).
The getForm method looks like this:
private function getForm(Request $request)
{
$categoryTitle = 'NONE';
$categoryId = $request->request->get('advert', false)['category'];
if ($categoryId) {
$categoryTitle = $this->getDoctrine()->getRepository('Bundle:Category')->find($categoryId)->getTitle();
}
return $this->createForm($this->getFormType($categoryTitle), $this->getEntity($categoryTitle));
}
here I retrieve the categoryID (that is selected in the form in the request) and retreives the formType with getFormTypeand the entity with getEntity.
private function getEntity($categoryTitle)
{
$entity = new Advert();
switch ($categoryTitle) {
case Category::CARS:
$entity = new AdvertCar();
}
return $entity;
}
private function getFormType($categoryTitle)
{
switch ($categoryTitle) {
case Category::CARS:
return new AdvertCarType();
default:
return new AdvertType();
}
}
To be able to update this "on the fly" with ajax (but it also works if the user tries to submit the form) I created another action in the controller.
This action renders the parts of the form that I want to update (on ajax call), I do this by actually picking out what I don't need in the form with twig setting the form objects to rendered like so:
{% do form.title.setRendered %}
(this is just an example I actually do this for all the form objects that I don't want to render.
I then simply just call:
{{ form_rest(form) }}
which will retrieve the "rest" of the form which is different for different categories.
Now let's say you have state and than town to select. First select the state then you render the towns for that state in twig (but then you can actually just render the part you need, e.g {{ form_row(form.towns) }} and you return this rendered template as a json-response and just put it in the div you want with jquery.
$html = $this->renderView('#Bundle/NewAddPage/filter_area.twig', array('form' => $form->createView()));
and then returning the $html variable in the response.
I hope this helps, and that the explanation is good enough, if not just make a comment and I'll update this with my answer!
I am using Symfony with propel to generate a form called BaseMeetingMeetingsForm.
In MeetingMeetingsForm.class.php I have the following configure method:
public function configure() {
$this->useFields(array('name', 'group_id', 'location', 'start', 'length'));
$this->widgetSchema['invited'] = new myWidgetFormTokenAutocompleter(array("url"=>"/user/json"));
}
In MeetingMeetings.php my save method is simply:
public function save(PropelPDO $con = null) {
$this->setOwnerId(Meeting::getUserId());
return parent::save($con);
}
However propel doesn't know about my custom field and as such doesn't do anything with it. Where and how to I put in a special section that can deal with this form field, please be aware it is not just a simple save to database, I need to deal with the input specially before it is input.
Thanks for your time and advice,
You have to define a validator (and/or create your own). The validator clean() method returns the value that needs to be persisted.
In Doctrine (I don't know Propel) the form then calls the doUpdateObject() on the form, which in turns calls the fromArray($arr) function on the model.
So if it's already a property on your model you'll only need to create the validator. If it's a more complex widget, you'll need to add some logic to the form.
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.