I have been trying to create an extremely basic symfony form (used for search functionality) with only one input. It uses GET method on submit. It seems to work as expected, however it generates an extremely ugly and unnecessarily long URL. I have been trying to 'clean' the URL up for a quite a while now, I was wondering if someone ran into the same problem and knows how to fix it?
Form
$form = $this->createFormBuilder($search)
->setMethod('GET')
->add('q', 'text')
->add('search', 'submit')
->getForm();
On submit the form generates the following URL:
search?form[q]=red+apple&form[search]=&form[_token]=bb342d7ef928e984713d8cf3eda9a63440f973f2
Desired URL:
search?q=red+apple
Thanks in advance!
To create your desired URL, you will have to set the form name by using createNamedBuilder which you'll just leave blank ''.
To remove _token you need to set csrf_protection to false. Please look into csrf protection to make sure you know what could happen if it is turned off.
Changing your code to the following should give you the results you want.
$form = $this->get('form.factory')->createNamedBuilder('', 'form', $search, array(
'csrf_protection' => false,
))->setMethod('GET')
->add('q', 'text')
->add('search', 'submit')
->getForm();
This should produce a URL like:
search?q=red+apple&search=
Edit:
If you want to get rid of &search=, one way would be to change search from submit to button.
->add('search', 'button')
This will require javascript to submit your form.
Here is simple example in jquery:
//This assumes one form and one button
$(document).ready(function(){
$('button').click(function(){
$('form').submit();
});
});
This will produce a URL like:
search?q=red+apple
To access GET vars you put something like this in your controller:
public function yourSearchAction(Request $request)
{
// your code ...
$form->handleRequest($request);
if ($form->isValid()) {
$getVars = $form->getData();
$q = $getVars['q'];
$page = $getVars['page'];
$billing = $em
//Do something
}
return //your code
}
Just to clarify if you are adding page to your URL you will need to add it to your form:
->add('page', 'text')
Old question but, for people who want to know, this does the job too (Symfony 2.8) :
<?php
// src/AppBundle/Form/SearchType.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('GET')
->add('q', TextType::class)
->add('submit', SubmitType::class))
;
}
public function getBlockPrefix(){
return '';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
}
In your controller :
<?php
//...
use AppBundle\Form\SearchType;
//...
public function yourSearchAction(Request $request)
{
$form = $this->createForm(SearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$q = $form->get('q')->getData();
// ...
}
// ...
}
Related
I have a somewhat complex form and am struggling with adding a ModelTransformer to a dynamically added field.
First I have a basic form with some fields and one CollectionType field that includes a custom Type:
class FilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// adding some other fields here ...
$builder->add('conditions', Type\CollectionType::class, [
'entry_type' => FilterRowType::class,
'allow_add' => true,
'prototype' => true,
'allow_delete' => true,
'entry_options' => ['label' => false],
]);
}
}
The FilterRowType consists of several fields that are depending on each other.
First the user has to select an option from a dropdown and then another field is added whose type and options depend on the selected value of the first field.
The second field could be TextType or NumberType or even ChoiceType with its choices again depending on the first field.
Finally I need to add a CallbackTransformer to this second field.
So here is what I currently have (widely stripped of stuff I think is not important for this question):
class FilterRowType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('attribute', Type\ChoiceType::class, [
'choices' => $this->getAttributeChoices(),
]);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
$this->addDynamicInputs($event, $builder);
});
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($builder) {
$this->addDynamicInputs($event, $builder);
});
}
public function addDynamicInputs(FormEvent $event, FormBuilderInterface $builder)
{
$form = $event->getForm();
$data = $event->getData();
// adding some other fields ...
$valueConfig = $this->getValueConfig($data['attribute']);
$form->add('value', $valueConfig['type'], $valueConfig['options']);
$valueConfig['options']['auto_initialize'] = false;
$form->add(
$builder->create('value', $valueConfig['type'], $valueConfig['options'])
->addModelTransformer($this->getCallbackTransformer ())
->getForm()
);
}
}
And this is actually working ! :)
BUT:
As you might already have spotted I am actually adding the 'value' field twice here.
This happened by accident as I added the CallbackTransformer later and forgot to delete the original line.
The problem is that if I now remove the original line $form->add('value', $valueConfig['type'], $valueConfig['options']); I run into an exception:
Neither the property "value" nor one of the methods "value()", "getvalue()"/"isvalue()"/"hasvalue()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".
Probably because I set $valueConfig['options']['auto_initialize'] = false; for the new creation of the field?
But if I remove that line I run into a different error:
Automatic initialization is only supported on root forms. You should set the "auto_initialize" option to false on the field "value"
Of course I could leave everything as it is with adding the 'value' field twice.
But that seems a very fishy solution to me and I am afraid that it might have some unforeseen consequences even if currently everything seems to work fine.
So can maybe someone with more insight into symfony forms enlighten me?
Are there possible problems with my 'solution' ?
Is there a better/proper way of doing what I am trying to do?
I had the same case today. Here is what I did :
Create an extension :
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ModelTransformerExtension extends AbstractTypeExtension {
public static function getExtendedTypes(): iterable {
return [FormType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
if (isset($options['model_transformer'])) {
$builder->addModelTransformer($options['model_transformer']);
}
}
public function configureOptions(OptionsResolver $resolver) {
parent::configureOptions($resolver);
$resolver->setDefaults(array('model_transformer' => null));
}
}
Use it in your form field options :
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->add('fieldName', $TextType::class, [
'model_transformer' => // Your transformer here
]);
});
I've been getting errors when I press update.
I'm using the same code as adding.
I think it may be the picture upload that is causing the problem.
Here's the error:
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string.
You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.
My Controller:
public function updateAction(Request $request ,$id)
{
$em = $this->getDoctrine()->getManager();
$forum = $em->getRepository("ForumBundle:Forum")->find($id);
$forum->setModifiee(new \DateTime('now'));
$form = $this->createForm(ForumType::class, $forum);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $form['image']->getData();
$file->move("images/", $file->getClientOriginalName());
$forum->setImage("images/" . $file->getClientOriginalName());
$em->persist($forum);
$em->flush();
return $this->redirectToRoute('forum_show');
}
return $this->render("#Forum/Sujet/Update_topic.html.twig", array("form" => $form->createView()));
}
My Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('titre')
->add('tags')
->add('image', FileType::class,array('attr' => array(
'class'=>'form-control'
//'class'=>'btn btn-default btn-file'
)))
->add('blog',TextareaType::class)
->add('Ajouter',SubmitType::class,array('attr' => array(
'class'=>'theme_button color3 wide_button'
)));
}
These are my includes:
namespace ForumBundle\Controller;
use blackknight467\StarRatingBundle\Form\RatingType;
use ForumBundle\Form\RateType;
use ForumBundle\Entity\Commentaire;
use ForumBundle\Entity\Forum;
use ForumBundle\Entity\Rating;
use ForumBundle\Form\CommentaireType;
use ForumBundle\Form\ForumType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\File;
According to the Symfony documentation : How to upload file, you have to set a File Object into Image setter, not a string. That'a why you have this error :
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string.
You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.
The rigth way to accomplish this is to set a new File instance to your image on your controller object, like this :
use Symfony\Component\HttpFoundation\File\File;
// ...
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$forum = $em->getRepository("ForumBundle:Forum")->find($id);
$forum->setModifiee(new \DateTime('now'));
$form = $this->createForm(ForumType::class, $forum);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$file = $form['image']->getData();
$file->move("images/", $file->getClientOriginalName());
$forum->setImage("images/" . $file->getClientOriginalName());
$forum->setImage(new File($this->getParameter('images_directory').'/'.$forum->getImage()));
$em->persist($forum);
$em->flush();
return $this->redirectToRoute('forum_show');
}
}
// ...
The parameter images_directory have to be set in the services.yml file, according to the symfony's documentation :
# config/services.yaml
# ...
parameters:
# depending of your symfony version
images_directory: '%kernel.project_dir%/web|public/uploads/images'
Another way is to use an uploader service or a dedicated bundle like : VichUploaderBundle or OneupUploaderBundle
Hope this help and have a nice day !!!
In symfony 2.8 in updateAction just correct define fileType for $id
I think it should look like this:
public function updateAction(Request $request ,$id) {
$id->setImage(
new File($this->getParameter('images_directory').'/'.$id->getImage()
));
...
I am trying to create a form with multiple steps in that:
First field displayed should be a phone number type.
Once that is submitted I want to perform a lookup to see if there is a user with that phone number.
If a user is found then I need a second field for verification, so I create a zip code field.
I am currently doing this with one controller and one form type:
SearchType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('phoneNumber', PhoneNumberType::class, array(
'default_region' => 'US',
'format' => PhoneNumberFormat::NATIONAL))
->add("finished", HiddenType::class, array(
"data" => "no"))
->add("submit", SubmitType::class);
$formModifier = function (FormInterface $form) {
$phoneNumber = $form->get('phoneNumber')->getData();
$one = $this->em->getRepository('AppBundle:Phone')->findOneByPhoneNumber($phoneNumber);
if($one){
$form->remove("submit");
$form->remove("finished");
$form->add("zipCode", TextType::class);
$form->add("finished", HiddenType::class, array(
"data" => "yes"));
$form->add("submit", SubmitType::class);
}
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$formModifier($event->getForm());
}
);
$builder->get('phoneNumber')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$formModifier($event->getForm()->getParent());
}
);
}
Controller:
$form = $this->createForm(SearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() && $form->get("finished")->getData() == "Yes") {
//...
}
So, I'm not even sure if I am on the right track. I am currently trying to use the Hidden field "finished" to let me controller know when to actually do the work with the submitted data, however "finished" never gets set to "yes" and I am not sure why.
Aside from that, is there a better way for me to be doing this?
I looked into CraueFormFlowBundle but that required the underlying data to be an object (wouldn't work with Array) and I didn't think I wanted it to be in this case since I am just using the data entered to perform a query.
I'm trying to do as easy thing as make the date field not to be set under "now" value. I'd emphasise that this date field is not linked to any Entity. Symfony keeps ignoring constraint even though I set it in two places:
1. Constraint set in the class definition
This is the first thing I've done. I decided to put this constraint directly where I define this date field
namespace Asmox\BubblechecklistBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
class DeadlineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('date', 'datetime',array(
'mapped' => false,
'date_format' => 'dd MMMM yyyy',
'constraints' => array(
new LessThanOrEqual("now")
)
));
}
2. Constraint set in the validation.yml file
I also added constraint into separate file, because the first way didn't give the results:
Asmox\BubblechecklistBundle\Form\Type\DeadlineType:
properties:
date:
- LessThanOrEqual: now
I ensured that I have enabled validation in both my configuration files (for prod and dev):
validation: { enabled: true, enable_annotations: true }
But every time I try to submit form with date earlier than now, Symfony pass it. I don't know what I can do more. Please take a look on my paste from Controller:
public function newTaskAction(Request $request)
{
// ...
$form = $this->createForm(new TaskType());
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
// Add new task...
// Redirect to the task list
$this->addFlash('notice', 'I've added new task!');
return $this->redirectToRoute('tasksList');
}
else {
// Create view with form if form is not validate
$request = $this->getRequest();
$request->setLocale('pl');
if ($mobileDetector->isMobile())
$template = 'AsmoxBubblechecklistBundle:Task:taskFormPhone.html.twig';
else
$template = 'AsmoxBubblechecklistBundle:Task:taskForm.html.twig';
return $this->render($template, array('form'=>$form->createView()));
}
}
else {
// Create view with form if request isn't POST
$request = $this->getRequest();
$request->setLocale('pl');
// Utwórz widok
if ($mobileDetector->isMobile())
$template = 'AsmoxBubblechecklistBundle:Task:taskFormPhone.html.twig';
else
$template = 'AsmoxBubblechecklistBundle:Task:taskForm.html.twig';
return $this->render($template, array('form'=>$form->createView()));
}
}
Whats wrong?
I have a drop down form element. Initially it starts out empty but it is populated with values via javascript after the user has made some interactions. Thats all working ok. However when I submit it always returns a validation error This value is not valid..
If I add the items to the choices list in the form code it will validate OK however I am trying to populate it dynamically and pre adding the items to the choices list is not going to work.
The problem I think is because the form is validating against an empty list of items. I don't want it to validate against a list at all. I have set validation required to false. I switched the chocie type to text and that always passes validation.
This will only validate against empty rows or items added to choice list
$builder->add('verified_city', 'choice', array(
'required' => false
));
Similar question here that was not answered.
Validating dynamically loaded choices in Symfony 2
Say you don't know what all the available choices are. It could be loaded in from a external web source?
after much time messing around trying to find it. You basically need to add a PRE_BIND listener. You add some extra choices just before you bind the values ready for validation.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// .. create form code at the top
$ff = $builder->getFormFactory();
// function to add 'template' choice field dynamically
$func = function (FormEvent $e) use ($ff) {
$data = $e->getData();
$form = $e->getForm();
if ($form->has('verified_city')) {
$form->remove('verified_city');
}
// this helps determine what the list of available cities are that we can use
if ($data instanceof \Portal\PriceWatchBundle\Entity\PriceWatch) {
$country = ($data->getVerifiedCountry()) ? $data->getVerifiedCountry() : null;
}
else{
$country = $data['verified_country'];
}
// here u can populate choices in a manner u do it in loadChoices use your service in here
$choices = array('', '','Manchester' => 'Manchester', 'Leeds' => 'Leeds');
#if (/* some conditions etc */)
#{
# $choices = array('3' => '3', '4' => '4');
#}
$form->add($ff->createNamed('verified_city', 'choice', null, compact('choices')));
};
// Register the function above as EventListener on PreSet and PreBind
// This is called when form first init - not needed in this example
#$builder->addEventListener(FormEvents::PRE_SET_DATA, $func);
// called just before validation
$builder->addEventListener(FormEvents::PRE_BIND, $func);
}
The validation is handled by the Validator component: http://symfony.com/doc/current/book/validation.html.
The required option in the Form layer is used to control the HTML5 required attribute, so it won't change anything for you, and that is normal.
What you should do here is to configure the Validation layer according to the documentation linked above.
Found a better solution which I posted here: Disable backend validation for choice field in Symfony 2 Type
Old answer:
Just spent a few hours dealing with that problem. This choice - type is really annoying. My solution is similar to yours, maybe a little shorter. Of course it's a hack but what can you do...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('place', 'choice'); //don't validate that
//... more form fields
//before submit remove the field and set the submitted choice as
//"static" choices to make "ChoiceToValueTransformer" happy
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if ($form->has('place')) {
$form->remove('place');
}
$form->add('place', 'choice', array(
'choices' => array($data['place']=>'Whatever'),
));
});
}
Add this inside buildForm method in your form type class so that you can validate an input field value rather a choice from a select field value;
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
if ($form->has('verified_city')) {
$form->remove('verified_city');
$form->add(
'verified_city',
'text',
['required' => false]
)
}
}
);
Update in Validations.yml
Kindly update the Validation.yml file in the below format : setting the group names in the each field
password:
- NotBlank: { message: Please enter password ,groups: [Default]}
Update in Form Type
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'RegistrationBundle\Entity\sf_members',
'validation_groups' => function(FormInterface $form){
$data = $form->getData();
$member_id = $data->getMemberId();
// Block of code;
// starts Here :
if( condition == 'edit profile') {
return array('edit');
} else
{
return array('Default');
}
},
Update in Entity
/**
* #var string
*
* #ORM\Column(name="password", type="text")
* #Assert\Regex(
* pattern="/(?i)^(?=.[a-zA-Z])(?=.\d).{8,}$/",
* match=true,
* message="Your password must be at least 8 characters, including at least one number and one letter",
* groups={"Default","edit"}
* )
*/
private $password;