Symfony2 form - how overwrite field with default value - php

I have a form with one default value:
class GearType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('options')
->add('model', 'choice', array('choices' => $this->getModelChoices(), 'data' => 2));
}
one of the requirements is form can be pre-populated by re-sellers by passing parameters in URL. It is also nice feature for potential customers to copy and paste link to email, communicators, etc.
I did it this way:
/**
* #Route("/car/gear")
* #Template()
*/
public function gearAction(Request $request)
{
$form = $this->createForm(new GearType());
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
return 'is valid';
}
} else {
$get = $this->getRequest()->query->all();
if (!empty($get)) {
$normalizer = new GetSetMethodNormalizer();
$form->setData($normalizer->denormalize($get, new Gear())); # look here
}
}
return array('form' => $form->createView());
}
unfortunately field 'options' has always default value, instead value passed as a parameter.
I have tried to change line # look here into
$gear = $normalizer->denormalize($get, new Gear());
$form = $this->createForm(new GearType(), $gear);
but no result.
It seems that solution is passing additional parameter to GearType object. I do not like this solution. Does anyone know better way?

Add this snippet, and modifiy between the [ ] as appropriate
$form->bind($request);
if ( [ passed parameters from querystring ] ){ //// New Code
$form->getData()->setOptions( [ processed parameter ]); //// New Code
} //// New Code
if ($form->isValid()) {
return 'is valid';
}

The reason for the field options always having default value may be the actual query. Instead of denormalizing and setting the data directly, modify else fragment to:
} else {
$form = $this->createForm(new GearType(), new Gear(), array(
'validation_groups' => array('not-validating')
));
$form->bind($request);
}
The form will validate only against validations associated with the not-validating group, which will avoid showing the common required alerts if the form is built form GET.
Docs about 'validations-groups': http://symfony.com/doc/current/book/forms.html#validation-groups
The question is similar to: Entity form field and validation in Symfony2?

Related

Symfony 2 - Layout embed "no entity/class form" validation isn't working

I'm developing a blog in symfony and i'm stuck with forms that are embed inside the layout. In my case a simple search form.
<div class="b-header-block m-search">
{{ render(controller('YagoQuinoySimpleBlogBundle:Blog:searchArticles')) }}
</div>
To render the form i'm using an embed controller inside the layout twig file.
public function searchArticlesAction(Request $request)
{
$form = $this->createForm(new SearchArticlesType());
$form->handleRequest($request);
if ($form->isValid()) {
// Do stuff here
}
return $this->render('YagoQuinoySimpleBlogBundle:Blog:searchArticles.html.twig', array(
'form' => $form->createView()
));
}
The indexAction is the one that retrieves the form data and filters a list of articles.
public function indexAction(Request $request)
{
$form = $this->createForm(new SearchArticlesType());
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
$criteria = array(
'title' => $data['search']
);
} else {
$criteria = array();
}
$articles = $this->getDoctrine()->getRepository('YagoQuinoySimpleBlogBundle:Article')->findBy($criteria, array(
'createDateTime' => 'DESC'
), 5);
return $this->render('YagoQuinoySimpleBlogBundle:Blog:index.html.twig', array('articles' => $articles));
}
SearchArticlesType is a form class
class SearchArticlesType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('search', 'text', array(
'constraints' => new NotBlank()
))
->add('submit', 'submit', array(
'label' => 'Buscar'
));
}
public function getName()
{
return 'searchArticles';
}
}
The problem comes when i submit this form. The indexAction do his part, validating the form and filtering the articles but when the embed controller tries to validate data (just for displaying info or whatever)
$form->handleRequest($request);
if ($form->isValid()) {
// Do stuff here
}
I feel like i'm missing something.
Thank you for your help!
When you call render(controller('your_route')) you are actually making a sub request which means the parameters bags are emptied so your request isn't "handled" by the form.
If you are using 2.4+ you could get the master request from the request stack using ..
/** #var \Symfony\Component\HttpFoundation\RequestStack $requestStack */
$requestStack = $this->get('request_stack');
$masterRequest = $requestStack->getMasterRequest();
And then you could handle that request in your rendered controller as opposed to the current (sub) request like..
$form->handleRequest($masterRequest);
In your: public function searchArticlesAction(Request $request) you're missing second argument on create form
$searchArticle = new SearchArticle(); // I assume this is how you named the Entity, if not just change the entity name
$form = $this->createForm(new SearchArticlesType(), $article);

Add a required form field based on submitted data in Symfony2

I have a form with a status select. If a certain status is selected and the form is submitted it should reload and require an additional field.
I have read Dynamic generation for submitted Forms and almost every other post on the internet and about this topic and tried different event combinations (and got different errors) but I still struggle to make this to work correctly.
This is what I have so far:
FormType
private function addProcessAfterField(FormInterface $form)
{
$form->add('processAfterDate', 'date', array('required' => true));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', 'entity', array(
'class' => 'Acme\Bundle\ApplicationBundle\Entity\LeadStatusCode',
'choices' => $this->allowedTypes
));
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$form = $event->getForm();
$data = $event->getData();
if ($data->getStatus()->getId() == LeadStatusCode::INTERESTED_LATER) {
$this->addProcessAfterField($form);
}
});
$builder->get('status')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event){
$data = $event->getData();
if ($data == LeadStatusCode::INTERESTED_LATER && !$event->getForm()->getParent()->getData()->getProcessAfterDate()) {
$this->addProcessAfterField($event->getForm()->getParent());
}
});
$builder->add('comment', 'textarea', array('mapped' => false));
$builder->add('Update', 'submit');
}
Error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Proxies\__CG__\Acme\Bundle\ApplicationBundle\Entity\Lead::setProcessAfterDate() must be an instance of DateTime, null given, called in /var/www/application.dev/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/application.dev/app/cache/dev/doctrine/orm/Proxies/__CG__AcmeBundleApplicationBundleEntityLead.php line 447
As already mentioned I tried different event combinations, one was almost working but then the date was never persisted to the entity so I added the \DateTime type-hint to the setProcessAfterDate() method. I am not sure if I don`t understand the event system correctly or if the error lies somewhere else.
Well, it might not be the best way to solve it, but to make long story short:
$form->handleRequest($request);
if($form->isValid()) // check if the basic version of the form is ok
{
$form = $this->createForm(new XXXXForm(), $form->getData()); // you recreate the form with the data that was submitted, so you rebuild the form with new data
if($form->isValid())
{
// ok
}
// not ok
}
Then inside buildForm function, you base the "required" attribute value of fields based on what you want:
'required' => $this->getCheckRequired($options)
private function getCheckRequired($options) // checks whether field should be required based on data bound to the form
{
if($options && isset($options['data'])
{
switch $options['data']->getStatus():
// whatever
;
}
return false;
}
As I said, this is not the best solution, and it doesn't fix your approach, but rather proposes a different one, but it does the job

Symfony2 invalid dynamic form with no error

I got a problem with a dynamic form on symfony2. I'm trying to generate some fields for a submitted form. In others words, the user enters some values, submits the form, and according to these values, my dynamics fields are added to this same form (which is, obviously, displayed a second time). To do that, I used this example from the cookbook : http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data
So, here is my FormationType class
class FormationType extends AbstractType
{
private $em;
private $context;
public function __construct($em, $context) {
$this->em = $em;
$this->context = $context;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('date')
->add('type', 'choice', array(
'mapped' => false,
'choices' => Formationlist::getTypeTypes(false),
'empty_value' => false,
))
->add('cost')
->add('travelCost')
->add('maximum')
->add('location')
->add('schedule')
;
$formModifier = function(FormInterface $form, $type) {
$formationList = $this->em->getRepository('CoreBundle:FormationList')->findBy(array("year" => 1, "type" => $type));
$form->add('formationList', 'entity', array(
'label'=> 'Titre formation',
'choices' => $formationList,
'class' => 'CoreBundle:FormationList',
'property' => 'title',)
);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($formModifier) {
$data = $event->getForm();
$type = $data->get('type')->getData();
$formModifier($event->getForm(), $type);
}
);
$builder->get('type')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($formModifier) {
$type = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $type);
}
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EXAMPLE\CoreBundle\Entity\Formation'
));
}
public function getName()
{
return 'example_corebundle_formationtype';
}
}
So, the two addEventListener work pretty well. The first time my form is displayed, the field in formModifier is not loaded, as expected. My controller class is the following one :
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$contextSrv = $this->get('example.service.context');
$context = $contextSrv->getContext();
$entity = new Formation();
$form = $this->createForm(new FormationType($em, $context), $entity);
$form->bind($request);
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('formation_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
Since one of my dynamic field can't be null, the first time the form is submitted, it can't be valid. So, the FormationType is loaded a second time. That means, if the field "type" was filled, my formModifier() function can load the dynamic field (formationList). Until there, everything works pretty well, and I got my new field.
But, after a second "submit" on the form...nothing happen. The page is just reloaded, and no errors are displayed.
I checked the form content with
var_dump($request->request->get('example_corebundle_formationtype'));
-> Every fields (including the dynamic one) are filled with valid values.
I also try this :
foreach($form->all() as $item) {
echo $item->getName();
var_dump($item->getErrors());
}
-> These lines don't show any error. But, the form is never valid.
var_dump($form->isValid());
-> It returns false. So the form is invalid.
Finally, if I remove the whole dynamic part, my form works.
I don't understand what's wrong. There is no errors displayed by the form, and the csrf token seems right. Did I miss something ? Thanks for your help.
I know this is a bit outdated but comes up quite high on Google.
The getErrors() metod returns only Form's global errors not error messages for the underlying fields, you need either getErrors(true) or more sophisticated method when using embeded forms in a form. Please see: https://knpuniversity.com/blog/symfony-debugging-form-errors for more information.
There is probably a validation error lying somewhere in your form.
Instead of your complicated calls to Form::getErrors() - which is not fully recursive, as the errors of any field deeper than the 2nd level will not be displayed - you should use Form::getErrorsAsString().
This is a debug method created by the Symfony guys for developers such as you, trying to understand where a validation error could lie in complex forms.
If no error is displayed although it should, this may be a form theming error. When creating a custom form theme, it is possible that a developper overrides or forgets to display the error block of a field.
Another possible source of the problem lies is the general display of the form. If you display your form using {{ form_widget(form) }}, then any error that bubbles to the top form will never be displayed. Make then sure that you use {{ form_row(form) }} instead.
I also encountered this problem a few times.
In my case I posted data in JSON format, so I had to do a request listener with a high priority which transforms json data into normal POST data, which is available in $request->request.
One scenario where the $form is invalid and there is no errors in also when the post data is empty, try to make a dump of $request->request->all() to see if you have the data.

How to add some extra data to a symfony 2 form

I have a form for my entity called Book and I have a type to display a form in my view. In this type I have some fields that are mapped to properties in my entity.
Now I want to add another field which is not mapped in my entity and supply some initial data for that field during form creation.
My Type looks like this
// BookBundle\Type\Book
public function buildForm(FormBuilderInterface $builder, array $options = null)
{
$builder->add('title');
$builder->add('another_field', null, array(
'mapped' => false
));
}
The form is created like this
$book = $repository->find(1);
$form = $this->createForm(new BookType(), $book);
How can I supply some initial data now during form creation? Or how do I have to change that creation of the form to add initial data to the another_field field?
I also have a form that has fields that mostly match a previously defined entity, but one of the form fields has mapped set to false.
To get around this in the controller, you can give it some initial data pretty easily like this:
$product = new Product(); // or load with Doctrine/Propel
$initialData = "John Doe, this field is not actually mapped to Product";
$form = $this->createForm(new ProductType(), $product);
$form->get('nonMappedField')->setData($initialData);
simple as that. Then when you're processing the form data to get ready to save it, you can access the non-mapped data with:
$form->get('nonMappedField')->getData();
One suggestion might be to add a constructor argument (or setter) on your BookType that includes the "another_field" data, and in the add arguments, set the 'data' parameter:
class BookType
{
private $anotherFieldValue;
public function __construct($anotherFieldValue)
{
$this->anotherFieldValue = $anotherFieldValue;
}
public function buildForm(FormBuilderInterface $builder, array $options = null)
{
$builder->add('another_field', 'hidden', array(
'property_path' => false,
'data' => $this->anotherFieldValue
));
}
}
Then construct:
$this->createForm(new BookType('blahblah'), $book);
You can change the request parameters like this to support the form with additional data:
$type = new BookType();
$data = $this->getRequest()->request->get($type->getName());
$data = array_merge($data, array(
'additional_field' => 'value'
));
$this->getRequest()->request->set($type->getName(), $data);
This way your form will fill in the correct values for your field at rendering. If you want to supply many fields this may be an option.

How to render a form without a class in other service?

I want to generate a class-less form inside my service.
The way I do it is:
class StepSummary implements StepInterface
{
public function __construct($container)
{
$this->container = $container;
}
public function getVariables()
{
$form = $this->container->get('form.factory')->createBuilder('text')
->add('accept')
->getForm();
return array('form' => $form->createView());
}
}
In the API, I've found that I need to pass a form type to the FormBuilder - I didn't find any reference to that, so I've put imaginary text string. Now it renders the form but this way:
<input type="text" id="text" name="text" required="required" />
Obviously there is no reference to the accept field.
Controller's createForm() method was quite helpful here:
public function createFormBuilder($data = null, array $options = array())
{
return $this->container->get('form.factory')->createBuilder('form', $data, $options);
}
So the solution is:
$form = $this->container->get('form.factory')->createBuilder('form')
->add('accept')
->getForm();
Look at the chapter in the Symfony2 documentation called Using a Form without a Class.
Basically, you have to use createFormBuilder and instead of a string or an object you just pass an array with the default values.
From the documentation mentioned before:
// make sure you've imported the Request namespace above the class
use Symfony\Component\HttpFoundation\Request
// ...
public function contactAction(Request $request)
{
$defaultData = array('message' => 'Type your message here');
$form = $this->createFormBuilder($defaultData)
->add('name', 'text')
->add('email', 'email')
->add('message', 'textarea')
->getForm();
if ($request->getMethod() == 'POST') {
$form->bind($request);
// data is an array with "name", "email", and "message" keys
$data = $form->getData();
}
// ... render the form
}
If you don´t want to tie your form to any particular object, you don´t need to pass any object to the builder, you can do:
$form = $this->container->get('form.factory')->createBuilder()
->add('accept')
->getForm();
If you want to set some defaults for the form, you can tie the form to an array. For example:
$data['accept'] = 'default accept';
$form = $this->container->get('form.factory')->createBuilder($data)
->add('accept')
->getForm();

Categories