Symfony: many to many relation with text fields - forms

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.

Related

Creating collection of read-only entities in Symfony2

My question is about creating collection of entities. I know about "How To Embed Collection Forms" and successfully used it. But in this case I have:
Simple class
class Thing
{
/**
* #ORM\ManyToMany(targetEntity="DicStyle", mappedBy="things")
* ....
*/
protected $styles;
public function __construct()
{
$this->styles = new ArrayCollection();
}
}
Dictionary of styles
class DicStyle
{
.....
}
I don't need to create form for DicStyle objects, because this is read only objects = dictionary (unchangeable). So, I want to create a form with something like this:
$builder->add('styles', 'collection', array(
'type' => 'entity', 'options' => array(
'class' => 'MyEntityBundle:DicStyle'
)
))
Of course it's pseudo-code. I can not imagine how to implement it.
The result
Suppose, I have:
Table "Thing" with one row (id = 1).
Table "DicStyle" with 6 rows (id = from 1 to 6).
Table "mtm_thing_dicstyle" (many-to-many table)
In the form, I choose two DicStyle (id=3, id=5) for the Thing. So, the mtm_thing_dicstyle contains:
thing_id dicstyle_id
-------- ------------
1 3
1 5
Try this form:
$builder->add('styles', 'entity', array(
'class' => 'MyEntityBundle:DicStyle'
'property'=>'name' //what property do you want to see when you select,
'multiple" => true //you'll be able to select many DicStyle
'expanded' => false //it'll shown on a multiple choice select tag
)
);
To select one or more objects in a form to relate them to your result you can use the form field type "entity". (See form-types)
$builder->add('styles', 'entity', array(
'class' => 'MyEntityBundle:DicStyle',
'property' => 'name', // property you want to be displayed
'expanded' => true,
'multiple' => true
)
);
This would render checkboxes and therefore the possibility to select multiple entities to be referenced.
Keep in mind, if you use multiple => true there are two options:
expanded => false: renders a multiselect field
expanded => true: renders checkboxes
See form-entity-type-options

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

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();

Symfony Form choices customize the get url

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;
});
});

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,
));
}