Symfony Form choices customize the get url - forms

So I have a search bar form that I need to temporarly wire up to a legacy non-symfony page.
the current get url looks like the following (url-decoded)
http://localhost:9090/lagacy_page?query=test&platforms[]=Mac,Windows
but I need to make the url look like the following
http://localhost:9090/lagacy_page?query=test&platforms=Mac,Windows
Symfony is making platforms an array, does anyone no if there is a way to force it to be a comma delimitated list?
Here is the buildForm method
/**
* method to build search bar form
*
* #param \Symfony\Component\Form\FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// the platform selector
$builder->add('platform', 'choice',
['choices' => [
Platforms::ALL => 'All Software', // TODO: need to translate this
Platforms::WINDOWS => 'Windows',
Platforms::MAC => 'Mac',
Platforms::IOS => 'iOS',
Platforms::ANDROID => 'Android',
],
'multiple' => true,
'expanded' => true]);
// the actual search bar
$builder->add('query', 'search');
}

You will want to override how Symfony2 renders the choice field.
The documentation has plenty of information about how to customize Form rendering.
If only the choice type of search form needs this, you will need to create a custom type in order to avoid conflicts with the other forms of your website.
In short, if you override the choice type using the first doc and you do not use a custom type every choice type will use the same behavior (the one you will create for your search form) and you probably don't want that.
An easy alternative solution would be to apply a custom form_div_layout.html.twig file directly to the form object. There wouldn't be any conflicts with other forms as you would use a custom template just for the search form.
After reading the docs my answer will make more sense and you will be able to solve your problem.

You have to use two form elements, since Symfony doing it in right way (according to HTML specifcation)
/**
* method to build search bar form
*
* #param \Symfony\Component\Form\FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// the platform selector
$builder->add('platform_choice', 'choice',
['choices' => [
Platforms::ALL => 'All Software', // TODO: need to translate this
Platforms::WINDOWS => 'Windows',
Platforms::MAC => 'Mac',
Platforms::IOS => 'iOS',
Platforms::ANDROID => 'Android',
],
'multiple' => true,
'expanded' => true,
'attr' => [
'class' => 'platform-sorce'
])
->add('platform', 'hidden', [
'attr' => [
'class' => 'real-platform'
]
]);
// the actual search bar
$builder->add('query', 'search');
}
Then add JS updating you hidden field, since 'platform_choice' is disabled and wont be send.
$(function(){
var $real_platform = $('.real-platform'),
$platform_source = $('.platform-source');
$platform_source.change(function(){
$real_platform.val($(this).val().join(',');
});
$('#your-form").submit(function(){
$platform_source.attr('disabled', true);
return true;
});
});

Related

Symfony form that generates another form

I have a controller action the prompts the user for some free text input. When submitted the text is parsed into some number of objects that I want to put out on another form for the user to confirm that the initial parsing was done correctly.
Normally after dealing with the response to a form submission we call $this->redirectToRoute() to go off to some other path but I have all these objects laying around that I want to use. If I redirect off someplace else I lose them.
How can I keep them? I tried building my new form right there in the controller action method but then its submission does not seem to be handled properly.
/**
* #Route( "/my_stuff/{id}/text_to_objects", name="text_to_objects" )
*/
public function textToObjects( Request $request, Category $category ) {
$form = $this->createForm( TextToObjectsFormType::class, [
'category' => $category,
]);
$form->handleRequest( $request );
if( $form->isSubmitted() && $form->isValid() ) {
$formData = $form->getData();
$allTheStuff = textParserForStuff( $formData['objectText'] );
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
return $this->render( 'my_stuff/confirmation.html.twig', [
'form' => $nextForm->createView(),
'category' => $category,
] );
}
return $this->render( 'my_stuff/text.html.twig', [
'form' => $form->createView(),
'category' => $category,
] );
}
This does fine to the point of displaying the confirmation form but when I submit that form I just end up displaying the original TextToObjects form?
To answer albert's question, the TextToObjectsFormType just has three fields, a way to set the date & time for the group of generated objects, a way to select the origin of the objects and a textarea for the textual description. I do not set a data_class so I get an associative array back with the submitted information.
class TextToObjectsFormType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder
->add( 'textSourceDateTime', DateTimeType::class, [
'widget' => 'single_text',
'invalid_message' => 'Not a valid date and time',
'attr' => [ 'placeholder' => 'mm/dd/yyyy hh:mm',
'class' => 'js-datetimepicker', ],
])
->add( 'objectsOrigin', EntityType::class, [
'class' => ObjectSourcesClass::class,
])
->add( 'objectText', TextareaType::class, [
'label' => 'Copy and paste object description text here',
]);
}
}
How can I get the confirmed, potentially revised, objects back to put them into the database?
Thanks.
Using your current architecture
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
does not bear enough information. It should contain the action parameter to tell the submission to where route the post request.
In your StuffConfirmationFormType add an 'objectText' field hidden.
Create a confirmation_stuff route
Create an action for this route
In this action if the form is valid => save your stuff ELSE re render TextToObjectsFormType with an action linked to text_to_objects
Be aware that using this technique would not prevent a user to enter non functional data by manually editing the hidden field.
Hope this helps

Symfony: many to many relation with text fields

We have a many to many relation with two entity Product {properties: name, details } and Tag {properties: name}. Now when a user add product, he must be able to create tags as well. So I tried this
$builder->add('tags', 'entity', array(
'class' => 'AppBundle:Tag',
'multiple' => true,
'expanded' => true,
'property' => 'name',
'required' => true
'allow_add' => true ));
Unfortunately, this gives me a drop down list of tags. But what I really want is multiple text input fields where user can enter the name of the tags and then this should save in the database when user save the category form.
All solutions I found so far are all related to either drop down or checkbox choices. But in my case, I don't have a list of tags in the database and I want to create the tags when user create a product. So how can I do that?
The entity type field could be only select, checkboxes or radio buttons (depending on expanded and multiple options), so these are not option for you.
You can solve multiple text fields rendering by implementing Tag type and embed it to your form as a collection.
Alter ProductType by collection field:
...
$builder->add('tags', CollectionType::class, array(
'allow_add' => true,
'allow_delete' => true,
'entry_type' => TagType::class,
'entry_options' => array(
...
),
)
...
Create TagType itself:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tagName', FormType\TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Entity\Tag',
)
);
}
}
But be aware of downsides - if there are not any tags assigned to the Product, the won't be any fields rendered. You have basically two options - 1) add few empty tags to the Product entity before rendering the form, 2) implement some kind of javascript method to add new tag fields.

Maintain posted order in symfony2 choice input field (with choice list)

I'm using the Symfony2 framework in my project and use the Form component to create forms. I'm using the choice input field type to enable users to multi select options and I'm using a plugin to enable users to order these options.
Unfortunately the order of these options isn't maintained when posting the form to the controller. The request has the correct order by the Form component uses the order of the choices option.
How can I maintain the posted order using the Form component and choice input field type?
For the record, I did search on Google, Stackoverflow and at Github and I only found an issue about keeping the order of the preferred_choices (https://github.com/symfony/symfony/issues/5136). This issue does speak about a sort option but I can't find this option in the Symfony2 documentation.
I tried to solve same problem : it was needed to select several organizations and sort them in list.
And after $form->getData() my order from request was changed.
I made form event handlers and found that data have right order on FormEvents::PRE_SUBMIT event and I saved it in $this->preSubmitData.
After that, on FormEvents::SUBMIT event I overwrite data with wrong order (in real, it depends on order from choices option) from $this->preSubmitData. (You can remove array_merge from method)
class PriorityOrganizationSettingsType extends AbstractType {
private $preSubmitData;
/**
* #param FormBuilderInterface $builder
* #param array $options
* #throws \Exception
*/
public function buildForm(FormBuilderInterface $builder, array $options)
$builder
->add('organizations', 'choice', array(
'multiple' => 'true',
'required' => false,
'choices' => $this->getPriorityOperatorChoices(),
'attr' => [
'class' => 'multiselect-sortable',
'style' => 'height: 350px; width:100%;'
]
))
;
$builder->addEventListener(FormEvents::SUBMIT, array($this, 'submitEvent'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'preSubmitEvent'));
}
public function preSubmitEvent(FormEvent $event) {
$this->preSubmitData = $event->getData();
}
public function submitEvent(FormEvent $event) {
$event->setData(array_merge(
$event->getData(),
$this->preSubmitData
));
}
}

Symfony2 form collection -

I am building a gallery of images which must allow for tagging each image with keywords. To handle the tags, I'm using FPN/TagBundle (https://github.com/FabienPennequin/FPNTagBundle).
I've already built the form, using the following:
// UserAlbumImageType.php
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', null, array('label' => 'Description'))
//TODO: add tags
->add('tags', null, array(
'label' => 'Tags',
'mapped' => false,
'required' => false,
'attr' => array(
'class' => 'tags',
),
))
->add('licenseType', 'entity', array(
'label' => 'License',
'class' => 'VoxCoreBundle:LicenseType',
))
->add('privacyType', null, array('label' => 'Privacy'))
;
}$builder
->add('images', 'collection', array(
'type' => new UserAlbumImageType(),
'label' => false,
))
;
break;
...
// UserAlbumType.php
...
$builder
->add('images', 'collection', array(
'type' => new UserAlbumImageType(),
'label' => false,
))
;
break;
...
As you can see, the tags property is NOT mapped. This is because I don't want to write the tags into a field in the database, but instead persist them to a central tag table. And that's where the problem lies.
When the form is submitted, I'm simply calling $em->persist($userAlbum) which then persists changes to the UserAlbumImage objects in the collection. At this time, I'd like to grab the tags that were submitted via the form, and set them using the tag manager. I'm unsure where to handle this. In a Doctrine postPersist listener? If so, I'll still need to save the tags to the entity at least temporarily, then parse them. Is there a better way?
If I were you, I'd follow (as I'm always trying to do) the MVC pattern with added repositories. I'd implement a saveGallery method in the repository for the gallery entity. This would get called from the controller (similar as mansolux recommended, but instead having the store functionality in the controller (bad practice, if you ask me), call the repository method for it). The method would receive all the submitted data. It would first store all the entities that need to be stored before the tags (gallery, images, whatnot). After that I'd get the tag repository:
$repo = $this->em->getRepository("FPNTagBundle:TagEntityName");
Now, the only thing left to do is to store the tags using this repository. You can add some sanity checks to make sure the tag bundle you're using actually exists, but that's something for you to decide.
Hope it helps.
Why not in your controller :
// ...
$tags = $form->getData()->getTags();
foreach($tags as $tag) {
$em->persist($tag);
}
// ...
$em->flush();

Doctrine 2 result caching in Symfony with form type entity

I use APC result caching in docrine, and have filter form with type entity in all website pages and want cache this, but when I add useResultCache() to method I get exception
Entities passed to the choice field must be managed
example
...->getQuery()->useResultCache(true, null, 'someindex')->getResult()
but all action without form with entity type work normally.
Any ideas?
Don't know if You've figured out how to do it, but here's how I've done it (spent half a day figuring this out).
/* in FormType.php */
public function buildForm(FormBuilderInterface $builder, array $options)
{
$items = $options['entity_repository']
->findItems()
->useResultCache(true, 3600, 'my_cache')
->getResult();
$choice_list = new ObjectChoiceList($items, 'name', array(), null, 'id');
$builder->add('item', 'entity', array(
'class' => 'MyBundle:Items',
'multiple' => true,
'expanded' => true,
'choice_list' => $choice_list,
));
}