bind error to embedded form field in symfony2's controller - forms

Using Symfony2.3.4
As the title quite explains it I need to bind an error message to an embedded form field and preferably in the controller. I'm thinking maybe similar to how I do it with single forms:
use Symfony\Component\Form\FormError;
//....
public function createAction(){
//.....
$postData = current($request->request->all());
if ($postData['field_name'] == '') {
$error = new FormError("Write some stuff in here");
$form->get('field_name')->addError($error);
}
//.....
}
or maybe it's got to be done differently, either way I need help,
thank$

I see, that you are trying to display a message when a form field does not contain any value. You can do this easily in your form class, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('field_name', 'text', array(
'label' => 'Field label',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'Write some stuff in here.'))
),
));
}
If you need to inject some other kind of constraint to your form which is not part of Symfony2 framework, you can create your own validation constraint.
If you want to add some options to your form in controller, it can be done in the method where you create the form by setting your own options:
class YourController {
public function createForm(YourEntity $yourEntity){
$form = $this->createForm(new YourFormType(), $yourFormType, array(
'action' => $this->generateUrl('your_action_name',
array('your_custom_option_key' => 'Your custom option value')),
'method' => 'POST',
));
return $form;
}
// Rest of code omitted.
}
After that you need to add an option in setDefaultOptions(OptionsResolverInterface $resolver) method, in your form class, like this:
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
'your_custom_option_key' => '',
));
}
And then access it in buildForm(FormBuilderInterface $builder, array $options) method, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$options['your_custom_option_key']; // Access content of your option
// The rest of code omitted.
}

Related

Symfony Forms: Adding a CallbackTransformer to a field that is added in an EventListener

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

Can i pass an own array trough the options from controller to formType

I want to pass an array through this function:
$form = $this->createForm(ProductTypeType::class, $productType, $options);
In symfony4 it seams not possible to pass own parameter through the $otions to the formType.
It is possible as you describe. Here is an example:
$form = $this->createForm(
EntityType::class,
$entity,
['optionOne' => true] //this is the array of options (in this case just one)
);
In EntityType you could then use this option like this (for example, add a field):
if($options["optionOne"]){
$builder
->add('addedField')
//....or do something else...
Also in EntityType don't forget to set the default value for your option:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Entity::class,
'optionOne' => false,
]);
}

Symfony change form collection dropdown with propel model

Is it possible to change the contents of a form dropdown that is part of a form collection that is populated using propel but the data is not mapped. Example of code to get the data below:
AddressType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->add("addressOne", new addressOneType()),
->add("addressTwo", new addressTwoType(), array(
"required" => false,
)),
}
addressOneType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => CountryQuery::create()->find(),
))
->getForm();
}
This collection is used for a particular part of an application however in this part in need to call a service from the form itself. Would this be possible as I've tried to extend the ContainerInterface and declare this inside of the construct method however this just throws an error.
However, I beleive this to be due to the fact that the form builder is not declared as a service.
Is there an easier way of changing the data of the drop down menu by injecting a new model to override the original. For example:
$form = $this->createForm(new AddressType());
$newData = CountriesQuery::create()
->orderBy("different_field");
$form['collectionName']['fieldname']->setData($newData);
Doing the above doesn't change or override the original model that is changing the data. With or without the ->find() at the end of the $newData field.
Does anyone know of a way to overwrite the data set by the model?
A very simple way for pass specific options to form is in constructor ...
class addressOneType
{
protected $countryQuery;
public function __constructor( $countryQuery = null )
{
$this->countryQuery = $countryQuery;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->countryQuery ? $this->countryQuery :
CountryQuery::create();
$builder
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => $query->find(),
))
->getForm();
}
}
... and you can call to form in this way ...
$cQuery = CountriesQuery::create()->orderBy("different_field");
$form = $this->createForm(new AddressType($cQuery));

Symfony2 : Sort / Order a translated entity form field?

I am trying to order an entity form field witch is translated.
I am using the symfony translation tool, so i can't order values with a SQL statement.
Is there a way to sort values after there are loaded and translated ?
Maybe using a form event ?
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)
I found the solution to sort my field values in my Form Type.
We have to use the finishView() method which is called when the form view is created :
<?php
namespace My\Namespace\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
class MyFormType extends AbstractType
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
// Order translated countries
$collator = new \Collator($this->translator->getLocale());
usort(
$view->children['country']->vars['choices'],
function ($a, $b) use ($collator) {
return $collator->compare(
$this->translator->trans($a->label, array(), 'countries'),
$this->translator->trans($b->label, array(), 'countries')
);
}
);
}
// ...
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)
;
}
}
OLD ANSWER
I found a solution for my problem, I can sort them in my controller after creating the view :
$fview = $form->createView();
usort(
$fview->children['country']->vars['choices'],
function($a, $b) use ($translator){
return strcoll($translator->trans($a->label, array(), 'countries'), $translator->trans($b->label, array(), 'countries'));
}
);
Maybe I can do that in a better way ?
Originally I wished to do directly in my form builder instead of adding extra code in controllers where I use this form.
I think it's impossible. You need to use PHP sorting, but if you use Symfony Form Type, I would advise to sort it with JavaScript after page is loaded.
If your countries are in an array, just use the sort() function, with the SORT_STRING flag. You will do some gymnastic to have it in my opinion.
Check this doc : http://php.net/manual/fr/function.sort.php

Form validation - Require only one field to be filled in

I have the following form class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('existingfolder', 'entity', array(
'class' => 'ImageBundle:Folder',
'required' => false,
))
->add('folder', 'text', array('required' => false))
->add('file', 'file');
}
How can I set up the validation so that either the existingfolder or folder field must be filled (but not both of them)?
Any advice appreciated.
Thanks.
Use the True or Callback validation assert, here an example to check if the user must give at least one of the folders:
<?php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Image
{
// ...properties, functions, etc...
/**
* #Assert\True(message = "You must give at least an existing folder or a new folder")
*/
public function isThereOneFieldFilled()
{
return ($this->existingfolder || $this->folder); // If false, display an error !
}
}
Here another example if the user must give ONLY one field but not both:
/**
* #Assert\True(message = "You must give an existing folder or a new folder, not both")
*/
public function isThereOnlyOneFieldFilled()
{
return (!$this->existingfolder && $this->folder || $this->existingfolder && !$this->folder);
}
EDIT:
Callback validation inside a form (I found an example here):
// use ...
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
// Inside the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('existingfolder', 'entity', array(
'class' => 'ImageBundle:Folder',
'required' => false,
))
->add('folder', 'text', array('required' => false))
->add('file', 'file');
// Use the CallbackValidator like a TrueValidator behavior
$builder->addValidator(new CallbackValidator(function(FormInterface $form) {
if (!$form["existingfolder"]->getData() && !$form["folder"]->getData()) {
$form->addError(new FormError('You must give at least an existing folder or a new folder'));
}
}));
}