TranslatorInterface errors, can't use tooltips in form that is used in another - forms

I am trying to create tooltips for my addressType. I have the following for that.
use Symfony\Component\Translation\TranslatorInterface;
class AddressType extends AbstractType{
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add(
'country','text', array(
'label' => 'address.country',
'translation_domain' => 'messages' ,
'required' => true,
'attr' => array('class'=>"tooltipped",
'data-position' =>"bottom",
'data-delay '=>"50",
'data-tooltip' => $this->translator->trans('help.tooltips.address.country'))
I have done this multiple times for other forms and it works. However the thing is my addressType is being used for another form named customerType, which also houses tooltips and translator as well. the tooltips i use there work fine. But when I add the addresstype it crashes
->add(
'address', new AddressType(),array( // line 84
'label'=>false,
'required' => false,
The error I get is as followed:
Catchable Fatal Error: Argument 1 passed to AppBundle\Form\AddressType::__construct() must implement interface Symfony\Component\Translation\TranslatorInterface, none given, called in C:\Users\KevinDeLeeuw\Documents\GitHub\mountguru\src\AppBundle\Form\CustomerType.php on line 84 and defined
My questions how do I resolve this, or is such a thing not allowed?

You create new AddressType object in your custom form, instead of this pass form name (< v2.8) or class name AddressType::class (for symfony 2.8+)
->add('address', AddressType::class, [...])
And of course you need to define your address form type as service Defining your Forms as Services

Related

Symfony form Set Value of non mapped field from database

I have a TextType label field in my form, my Entity is EAV,
the structure is :
id,
targetId,
entityTargetName,
In my form I have a non mapped field label a dynamic from database, when rendering my form I must send a query to database with targetId and entityTargetName and get the value of label field and set it to the form.
I know that I can do query builder but my field is not a select, it's a TexType
->add('productName ', TextType::class,
array(
'mapped'=>false,
)
)
Any idea ?
You can inject entity repository not just for EntityType field.
You can do the following:
1. Create form in your controller in this way (or something similar):
/** #var FormInterface $form */
$form = $this->get('form.factory')->create(MyFormType::class, null, [
'action' => $this->get('router')->generate('my_action_routename'),
'method' => 'POST',
'entityManager' => $this->getDoctrine()->getManager(),
]);
2. Add configureOptions method to your MyFormType class:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MyModelClass::class,
'entityManager' => null, // This is important
]);
}
3. Use entity manager in this way in your buildForm method and pass it to default value:
$options['entityManager']->getRepository(ProductRepository::class)->getProductName(targetId, entityTargetName);
Hope this helped!

Array-type property and Form CollectionType with data transformer

I have an entity with a property that is set as an array
/**
* #ORM\Column(type="array")
*/
private $labels = [];
this array of data stores translations of a label like
[
'en' => 'foo-in-English',
'de' => 'foo-in-German',
'ru' => 'foo-in-Russian'
]
I have a Form with the type set for the labels like:
$builder
->add('labels', CollectionType::class);
Note that the entry_type defaults (properly) to TextType here. Left as is, the template would be displayed with text fields, like:
Labels: en: _____FOO IN ENGLISH____
de: _____FOO IN GERMAN_____
ru: _____FOO IN RUSSIAN____
But, I would like the fields to be displayed with the actual language name and not the two-letter code as the label, so something like:
Labels: English: _____FOO IN ENGLISH____
German: _____FOO IN GERMAN_____
Russian: _____FOO IN RUSSIAN____
I also want to make sure that all my selected/supported languages are displayed - even if they currently have no value.
So, this seems like the proper place for a DataTransformer, but try as I might I could not get this concept to work within the Form class. It seems that attempting to transform the data of a collection type is more difficult (or impossible?) than a simpler type like text.
I've overcome this as a workaround by transforming the data within the controller before submitting it to the form and after processing the form before persistence. e.g.
$this->transformTranslations($fooEntity);
$form = $this->createForm(FooType::class, $fooEntity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$fooEntity = $form->getData();
$this->reverseTransformTranslations($fooEntity);
$this->getDoctrine()->getManager()->persist($fooEntity);
$this->getDoctrine()->getManager()->flush();
...
I'm wondering if anyone has a better method (like how to use normal data or model transformers). I can't seem to find much online about using data transformers with collection types. TIA!
I have not personally used a doctrine array value before, however
you can define a 'default' form class for each of your translation options like so:
AppBundle\Form\LanguageStringEditorType.php
class LanguageStringEditorType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('en', TextareaType::class, ['label' => 'English'])
->add('de', TextareaType::class, ['label' => 'German'])
->add('ru', TextareaType::class, ['label' => 'Russian'])
;
}
}
If you keep the naming ('en', 'de' and 'ru') the same as your data array key names for example having an (doctrine) entity like this:
AppBundle\Entity\LanguageString.php
class LanguageString {
private $identifier;
private $translations; // this is the doctrine array type
// however I didn't feel like setting up a database for this
// test so I'm manually filling it see the next bit
... Getter and setter things ...
And create a type for that as well:
AppBundle\Form\LanguageStringType.php
class LanguageStringType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('identifier')
->add('translations', LanguageStringEditorType::class, ['label' => 'Translations'])
;
}
}
We can use this in the controller
$data = new LanguageString();
// fill some dummy content (not using database)..
$data->setIdentifier('hello_world');
$data->setTranslations([
'en' => 'Hello world!',
'de' => 'Hallo Welt!',
'ru' => 'Привет мир'
]);
$form = $this->createForm(LanguageStringType::class, $data);
return $this->render('default/index.html.twig', [
'form' => $form->createView()
]);
And the rest is done by magic, no transformers required. The data is placed in the form fields. And set to the entity when using the handleRequest. Just remember that the data key values are the same as the form builder names.
And as a bonus you have defined all your default language fields in the LanguageStringEditorType class, filled in or not.
So, I learned I needed to separate my two needs into different solutions. First I created a new form type to use instead of the text type I was using by default:
$builder
])
->add('labels', CollectionType::class, [
'entry_type' => TranslationType::class
])
This class is very simple and is only an extension of a regular TextType:
class TranslationType extends AbstractType
{
/**
* #var LocaleApiInterface
*/
private $localeApi;
/**
* TranslationType constructor.
* #param LocaleApiInterface $localeApi
*/
public function __construct(LocaleApiInterface $localeApi)
{
$this->localeApi = $localeApi;
}
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['label'] = array_search($view->vars['name'], $this->localeApi->getSupportedLocaleNames());
}
public function getParent()
{
return TextType::class;
}
}
This satisfied the labelling issue. Second, to ensure I had all the supported locales in my data, I used a FormEventListener:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$supportedLocales = $this->localeApi->getSupportedLocales();
$data = $event->getData();
$labels = $data['labels'];
foreach ($supportedLocales as $locale) {
if (!array_key_exists($locale, $labels)) {
$labels[$locale] = $labels['en'];
}
}
$data['labels'] = $labels;
$event->setData($data);
});
This adds the required keys to the data if they are not already present.

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

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

Symfony2 : Binding an expanded multiple choice form

I set up a form which includes a multiple expanded form
$builder->add('rooms', 'entity', array(
'class' => 'MyBundle:House',
'multiple' => true,
'expanded' => true,
'required' => false
));
The underlying class House has a rooms attribute defined as a many-to-many relation
/**
* #ORM\ManyToMany(targetEntity="RoomsType", cascade={"all"})
*/
private $rooms;
public function __construct()
{
$this->rooms = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addRooms($room)
{
$this->rooms[] = $room;
}
public function getRooms()
{
return $this->rooms;
}
When I render the form
{{ form_row(form.rooms }}
and then submit the form, I meet the following exception: Expected argument of type 'array' 'string' given (500 Internal Server Error)
If the form is not configured as a expanded, no exception is raised and the binding between the form and the underlying object works fine.
Any idea ?
Your targetEntity for your relationship has an odd name of RoomsType. Are you certain your Entity is RoomsType and not just Rooms? I would expect your RoomsType to define the form for your Rooms entity