Accessing all form fields during validation of Symfony2 form - php

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

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

Symfony2: dynamic generation of embedded form

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

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?

Why does the Symfony sets the form fields to null if they are not present in template but in formType

I am not able to understand why Symfony does this.
Suppose i have the UserFormType class
->add(username);
->add(datecreated);
Now i don't show dateCreated in my template then symfony sets the DateCreated to null which overwrites the database value.
I want to know what the problem in not setting that value to null and just have original value if its not in the template
From the documentation: Additionally, if there are any fields on the form that aren't included in the submitted data, those fields will be explicitly set to null. so you must render all your fields otherwise they will be set to null.
You can either not add it in the form and Symfony will leave that value alone on update, I'm guessing you set dateCreated in your code anyway so you don't need the Forms library trying to update that field.
The other option is to add the field as a hidden field ->add('dateCreated', 'hidden') you will need to render the field in your template and Symfony will keep around the dateCreated data. If you are rendering each row you can use form_rest(form) in your twig to render all the missing fields.
This is an old topic but I ran into the same situation and the easiest and cleanest way for me to handle this was to remove the field from the form when you don't want to do anything with it.
For example I want to show the list of groups a user is attached to but some other user can't see them. The result was any changes for those users would remove the group they already had.
The solution:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$this->onPreSubmit($event);
}
);
.....
}
public function onPreSubmit(FormEvent $event)
{
$params = $event->getData();
if (!isset($params['groups'])) {
$event->getForm()->remove('groups');
}
}
Now your form will reject the groups if it is set, to avoid that you can add to your form the options 'allow_extra_fields' => true. It will pass validation and do nothing with it.
Although it is neat I am not a big fan but I can't see any other solution, or you will have to build the html yourself and the action/service to handle validation without symfony built in form system.

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