Symfony: How to pass Doctrine Collection to Form as default values? - php

My action:
public function executeEdit(sfWebRequest $request)
{
// Get the object
$article = $this->getRoute()->getObject();
// Create the form
$this->form = new ArticleEditForm( $article );
}
You can see that $article is the Doctrine Collection that gets passed to the Form as default values. The $article object contains a fields like "title", "text", "author", etc.
But this causes an error in the Form creation:
500 | Internal Server Error | Doctrine_Record_UnknownPropertyException
Unknown record property / related component "_csrf_token" on "article"
So basically, the form is trying to use the Doctrine Collection to fill out default values for the form elements. But there obviously isn't a csrf_token in that object... But it's still trying to find one to use as the default value...
And what happens if you have a form where there are always extra empty fields that are empty but others have default values that are passed. If those empty fields don't have set values in the Doctrine Collection then you get an error...
Now, obviously I could just make a simple array ahead of time where I specify the default values and pass that:
$defaults = array( 'title' => $article->title, 'text' => $article->text, 'author' => $article->author );
$this->form = new ArticleEditForm( $defaults);
This works. My problem is that the above "article" is an example for simplicity reasons. In reality my form has about 30 fields in it. So the only way that this solution would work is to have to manually specify 30 individual default values in an array. This is a poor solution from a maintenance point-of-view, obviously.
I figure the Symfony developers are smart enough to come up with a good solution but I just can't find it... Any clues?

This problem is all about your form doesn't create _csrf_token. You can add it when creating form in your template page like:
<?php
echo $form->renderFormTag(url_for('article/edit'))
echo $form->render();
?>
<input type="hidden" name="form[_csrf_token]" value="<?= $form->getCSRFToken(); ?>">
<input type="submit" value="Submit" />
</form>

I discovered my problem. My custom form was extending BaseForm. Once I changed this to extend BaseArticleForm then I was able to successfully pass article Doctrine Collections in as default values.

Related

zf2/zf3 Hydrate Multiple Fieldsets with Objects

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()]);

Symfony2 Validating submitted data against Model class

I'm trying to validate submitted data against existing Model/Entity/POPO, however I can't get it to work in any simple way.
All of this is takes place inside a controller action.
So, I can do like this:
$constraints = new Assert\Collection([
'username' => [new Assert\NotBlank()],
'email' => [new Assert\Email()],
]);
$violationList = $this->get('validator')->validate($request->request->all(), $constraints);
However to do that in every action makes no sense, as having all constraints in a class would be a lot better. So, Validation component allows to do like this:
// All constraints are defined inside Customer class
$customer = new Customer();
$violationList = $this->get('validator')->validate($customer);
Violation list is full of errors now, as $customer is an empty object, but the problem is I can't find a way to use data from POST AND validate it against constraints that are defined in the class.
It is possible to write extra component/helper that would take POST data and then will call bunch of ->setUsername(), ->setEmail(), etc., but that doesn't seem right considering you can easily map Model to POST data, if:
Form component is involved;
OR using ConstraintsCollection manually;
Am I missing something obvious here or there is no out-of-the-box possibility? Thanks!
AFAIK the form component is the one responsible for mapping post data to your entity. So you have two choices
Use a form, like that you will have your data mapped and your model validated
Skip the form but then you have to map request params to your entity manually. then validate your model with $this->get('validator')->validate($customer);
Edit :
The form role is to map data coming from request ( html form , api .... ) to a model. Validation could be done with from or without it as its the validator component who does the job , it should be noted that the validation is done on the model and not the form.
If you want to skip the form check this question: Populate entity from data array without form/request although the form component is very useful specially if you are using the same logic in many places ( create / edit .. )

Symfony2: what is a "norm" data for a choice field type in forms

I'm working on a Symfony2 project where I have three fields that are populated dynamically which mean are built by AJAX call made in Angular JS controller. So the problem is, when I send the form I get the message:
This value is not valid.
So after do a lot of research (Symfony2 Nested Select in Spanish, Dynamic Form Modification from Symfony2 Docs, How to use Data Transformers from Symfony2 Docs, Post here in Stackoverflow, Entity as hidden field and many more pages I read) I found "almost" nothing so here is my question: what is a "norm data" for a choice field type? This is what my Type returns:
public function getParent() {
return 'choice';
}
public function getName() {
return 'country_choice';
}
For some reason the validation fails all the time and I suspect is due to choice type and since I don't know what "norm data" is for a choice type I can't work in proper way on DataTransformer. Could any point me in the right direction? Any advice or help?
I think it depends on the choices you set. For example, if you have a field called 'my_field' with choices:
'choices' => array('m' => 'Male', 'f' => 'Female')
and you set the post data with value 0, the field will be invalid because its value is not in the choices list.
http://symfony.com/doc/current/reference/forms/types/choice.html#choices

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.

Pre-load value into field in symfony form, getting error "Cannot Update form fields"

I have a form that is supposed to create a very simple new entry into the database. One of the fields in this table is related to another table. Specifically, there is an event that people attend, and I need to assign people to an event. Each event can have several people attending, and there can be several events.
When an admin user adds members to an event, I don't need them selecting the event on the actual form, this should be passed via the URL. e.g.
event/new/id/1
However, I am really struggling as to how to include that ID in the form. If I try and manually set the field value, I get the error Cannot Update Form Fields
e.g.
$this->['attendanceSuccess_id'] = 1;
If I try and hide the field:
$this->widgetSchema['attendanceSuccess_id'] = new sfWidgetFormInputHidden();
The form shows but obviously no value is passed and I get "that fields is required error"
This seems like a really simple and common thing to do, but I can't find any solutions! How can I pass a URL value into a form where the field points to another class?
Here's what I've done. Basically your form holds a hidden with the id, and your action gets it from the request and puts in into the form as an option. In the form you can also set the relationship to the Doctrine objects.
Your form should have something like (I don't know what your form should extend exactly since you are using Doctrine and mine was in Propel)
class SomethingForm extends BaseSomethingForm {
public function __construct($object = null, $options = array(), $CSRFSecret = null)
{
parent::__construct($object, $options, $CSRFSecret);
if (isset($options['person_id']))
{
$this->getObject()->setPersonId($options['person_id']);
$this->setDefault('person_id', $options['person_id']);
}
}
}
public function configure()
{
$this->setWidget('person_id', new sfWidgetFormInputHidden());
...more stuff...
}
Then your action says:
$this->form = new SomethingForm(null, array(
'person_id' => $request->getParameter('person_id')
));

Categories