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

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?

Related

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 .. )

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

Testing for POST in Yii 2.0

In my controllers that Gii creates it is common to see the following:
if($model->load(Yii::$app->request->post()) && $model->save()){
//.....do something such as redirect after save....//
}else
{
//.....render the form in initial state.....//
}
This works to test whether a POST is sent from my form && the model that I am specifying has saved the posted information (as I understand it).
I've done this similarly in controllers that I have created myself but in some situations this conditional gets bypassed because one or both of these conditions is failing and the form simply gets rendered in the initial state after I have submitted the form and I can see the POST going over the network.
Can someone explain why this conditional would fail? I believe the problem is with the 'Yii::$app->request->post()' because I have removed the '$model->save()' piece to test and it still bypasses the conditional.
Example code where it fails in my controller:
public function actionFreqopts()
{
$join = new FreqSubtypeJoin();
$options = new Frequency();
$model = new CreateCrystal();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->insertFreqopts();
return $this->redirect(['fieldmap', 'id' => $join->id]);
} else {
return $this->render('freqopts', ['join' => $join, 'options' => $options]);
}
}
My initial thought was that I'm not specifying the correct "$model" in that I'm trying to save the posted data to FreqSubtypeJoin() in this case and the $model is CreateCrystal(); however, even when I change the model in this conditional it still fails. It would be helpful if someone could briefly explain what the method 'load' is actually doing in layman's terms if possible.
The load() method of Model class is basically populating the model with data from the user, e.g. a post query.
To do this it firstly loads your array of data in a form that matches how Yii stores your record. It assumes that the data you are trying to load is in the form
_POST['Model name']['attribute name']
This is the first thing to check, and, as long as your _POST data is actually getting to the controller, is often where load() fails, especially if you've set your own field names in the form. This is why if you change the model, the model will not load.
It then check to see what attributes can be massively assigned. This just means whether the attributes can be assigned en-mass, like in the $model->load() way, or whether they have to be set one at a time, like in
$model->title = "Some title";
To decide whether or not an attribute can be massively assigned, Yii looks at your validation rules and your scenarios. It doesn't validate them yet, but if there is a validation rule present for that attribute, in that scenario, then it assumes it can be massively assigned.
So, the next things to check is scenarios. If you've not set any, or haven't used them, then there should be no problem here. Yii will use the default scenario which contains all the attributes that you have validation rules for. If you have used scenarios, then Yii will only allow you to load the attributes that you have declared in your scenario.
The next thing to check is your validation rules. Yii will only allow you to massively assign attributes that have associated rules.
These last two will not usually cause load() to fail, you will just get an incomplete model, so if your model is not loading then I'd suggest looking at the way the data is being submitted from the form and check the array of _POST data being sent. Make sure it has the form I suggested above.
I hope this helps!

Symfony/propel form saving an object with additional properties

First, let me say, that I find the sfFormPropel form's interface inconsistent.
There is bind(), which returns nothing, but triggers validation, save() which returns the saved object, and bindAndSave(), which returns boolean, actually the return value of isValid(). Now, I have a working application, but I don't feel the code is right, and I'm quite new to symfony, so perhaps I'm missing something.
The object I need to create needs some external properties, that are not presented in the form, are external to the model, and are handled by the application (for example, the userId of the user, that created the entity, an external-generated guid, etc.).
Right now the flow is as follows:
get values from request and bind them to form
check if form is valid
if it's valid, add additional values and bind them to form one more time
save the form and return the object
The obvious answer would to add application-specific values to the values, retrieved from request, but It does not make sense to bind the application-specific values if the form is not valid, since they can be potentially expensive operations, may create database records, etc. Additionally, it should not be possible to pass those values with the post request, they should come from application only.
Now, I though that I have to let the model do these things, but since the data is external to the model, action still need to pass it to the model. The problem is, if I call $form->getObject() after bind(), it still has the old data, and not the data submitted.
What is the correct way to implement this kind of post-processing?
Second bounty is started to award the other valuable answer
The correct way would be setting your default values on the object you are passing to the form constructor. For example if you want to set the logged in user id on an object you are creating:
$article = new Article();
$article->setUserId($this->getUser()->getId());
$form = new ArticleForm($article);
if ($request->isMethod('post')) {
$form->bind($request->getParameter('article'));
if ($form->isValid()) {
$form->save();
}
}
Likewise for existing object, you can load the record and change any properties before passing it to the form constructor.
EDIT:
If you want to modify the object after validating, you can use $form->updateObject() like Grad suggests in his response. If the generated values depend on the submitted values, you can override sfFormObject::processValues():
class UserForm {
public function processValues($values) {
$values['hash'] = sha1($values['id'] . $values['username']);
return parent::processValues($values);
}
}
In case you need something from the action, you can always pass it as an option to the form:
$form = new UserForm($user, array('foo' => $bar));
That way, you can use $this->getOption('foo') anywhere in your form code, eg. in processValues().
It kind of depends of who has "knowledge" about the extra attributes. If they're really request specific, thus need to be processed in the controller, I go for binding, testing if valid and then update the bound object. To get the updated object with the bound (and validated) fields use the updateObject function.
$form->bind(..)
if ($form->isValid()) {
$obj = $form->updateObject(); // Updates the values of the object with the cleaned up values. (returns object)
$obj->foo = 'bar';
$obj->save();
}
But since this normally is also behaviour that is form specific, I usually go for overriding the Form class. By overriding the doUpdateValues() function you can easily access submitted data, and append your own data. Of course you can also go higher in the chain, and override the save() function.
To set custom data for this form, you can also 'publish' public methods, which can then be used by the controller.

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