Form-wide error_bubbling in Symfony 2? - php

This is how i currently activate errors on my forms:
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title', null, array('error_bubbling' => true))
->add('content', null, array('error_bubbling' => true))
;
}
Is there a form-wide version?

No. In general you dont need to make errors bubble to parent form.
If you want to display all errors in one place, you can do this in the template.

If you are using the form types correctly (maybe don't let symfony guess it) then you should get error bubbling by default as seen here:
http://symfony.com/doc/current/reference/forms/types/text.html#error-bubbling
However If you are using a custom form type then you can set the default error_bubbling by default with configureOptions
final class CustomFormType extends AbstractType
{
/** {#inheritdoc} */
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
}
/** {#inheritdoc} */
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('label');
$resolver->setDefaults([
'error_bubbling' => false,
'compound' => true,
]);
}
}

Related

Names array contains duplicates: extends FormType Symfony

I have a form that I get from a bundle, but I need to add two extra fields. I am trying to extend the formType of the bundle but I get the following error:
An exception has been thrown during the rendering of a template
("Unable to render the form because the block names array contains
duplicates: "_order_errors", "order_errors", "order_errors",
"form_errors".").
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'token_id',
HiddenType::class,
[
'required' => false,
]
);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'inherit_data' => false,
'validation_groups' => false,
]);
}
public function getParent()
{
return FormOrderType::class;
}
}
I managed to fix it by changing the formtype name to a different one than the bundle name.

How to add options to a Symfony Form field?

I am rather new to PHP & Symfony, and am struggling with the form options:
I have the following, simple code:
//OnceType.php
class OnceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', TextType::class, [
"format" => "date"
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Once::class,
'format' => "date",
]);
}
}
I get an error because the format is not an option of TextType, but I cannot find a way to add my own options (But I know this is possible, from the others posts I read)
I have read a lot of other posts with similar issues, but cannot grasp how to do this (I tried the setDefaults options, but it didn't lead me anywhere)
What you need is to create a new custom extension which extends the TextType like this for example:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TextTypeExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['format'] = $options['format'];
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'format' => null,
]);
}
public static function getExtendedTypes(): iterable
{
return [TextType::class];
}
}
Read more here: https://symfony.com/doc/current/form/create_form_type_extension.html
You need to add a call to $resolver->setAllowedTypes() in your configureOptions() method.
See https://symfony.com/doc/current/forms.html#passing-options-to-forms
thanks for your help
I tried to do that, but still get the same error:
class OnceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', TextType::class, ["format" => "date"])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Once::class,
"format" => "date",
]);
$resolver->setAllowedTypes("format", "string");
}
}
EDIT: I think it is because the resolver there adds the options to form, instead of the option iteself ('date', the one added in buildForm)

Embedding nested form type fields in empty symfony form

I have form type for a Shop entity. This links in a 1-1 relationship to a ShopAddress entity.
When I nest the ShopAddress entity it appears blank when creating a new shop. How can I get this to render with the relevant blank fields when creating a new Shop?
// App/Froms/ShopType.php
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( "name" ) // rendering fine
->add( "shopAddress", CollectionType::class, [
"entry_type" => ShopAddressType::class, // no visible fields
] )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
// App/Froms/ShopAddressType.php
class ShopAddressType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("addressLine1", TextType::class ) // not rendering
->add("addressLine2") // not rendering
->add("postcode") // not rendering
->add("county"); // not rendering
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => ShopAddress::class,
));
}
}
Yep. Solved it. Docs had the answer You need to add it as a new FormBuilderInterface object in the add() method:
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", TextType::class)
->add(
// nested form here
$builder->create(
'shopAddress',
ShopAddressType::class,
array('by_reference' => true ) // adds ORM capabilities
)
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
Adding array('by_reference' => true ) will allow you to use the full ORM (Doctrine in my case) capabilities (e.g. $shop->getAddress()->setAddressLine1('this string to add')).

Symfony for propert_path for child form type

I understand if I put
->add('visitor.name', TextType::class, [
'property_path' => 'vistorName'
])
This will be identical to <input name="vistorName"
How can I do similar for child form, to have all the child field as a parent field name without putting in as a child array.
$builder->add('customer', CustomerType::class);
CustomerType
$builder
->add('name', TextType::class)
->add('email', EmailType::class)
->add('phone', PhoneNumberType::class, [
'default_region' => 'GB', // TODO GLOBAL release
'format' => PhoneNumberFormat::NATIONAL
]);
The above form should generate <input name="name"... <input name="email"... and not as following <input name="customer[name]"...
how can I do that?
Following is complete code example
// Cart model
class Cart {
protected $productName;
/** #var Customer */
protected $customer;
}
// Customer model
Class Customer {
protected $name;
protected $email;
protected $phone;
}
//CustomerType form
$builder
->add('name', TextType::class)
->add('email', EmailType::class)
->add('phone', PhoneNumberType::class, [
'default_region' => 'GB', // TODO GLOBAL release
'format' => PhoneNumberFormat::NATIONAL
]);
// main parent form
class MainFormType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('productName', TextType::class)
->add('customer', CustomerType::class); // need property_path for this, so all the inner fields can be used as parent
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Cart::class,
'required' => false
]);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'cart';
}
}
It works the other way around:
Scenario:
VisitorEntity:
TypeOfVisitorEntity
CustomerEntity
Now you want to update a VisitorEntity customer.name:
class MainFormType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('productName', TextType::class)
->add('customerName', TextType::class, ['property_path'=>'customer.name'])
->add('customerEmail', TextType::class, ['property_path'=>'customer.email'])
->add('customerPhone', TextType::class, ['property_path'=>'customer.phone'])
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Cart::class,
'required' => false
]);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'cart';
}
}
property_path
https://symfony.com/doc/3.4/reference/forms/types/form.html#property-path
type: any default: the field's name
Fields display a property value of the form's domain object by default. When the form is submitted, the submitted value is written back into the object.
If you want to override the property that a field reads from and writes to, you can set the property_path option. Its default value is the field's name.
If you wish the field to be ignored when reading or writing to the object you can set the property_path option to false, but using property_path for this purpose is deprecated, you should use the mapped option.

Passing data to buildForm() in Symfony 2.8, 3.0 and above

My application currently passes data to my form type using the constructor, as recommended in this answer. However the Symfony 2.8 upgrade guide advises that passing a type instance to the createForm function is deprecated:
Passing type instances to Form::add(), FormBuilder::add() and the
FormFactory::create*() methods is deprecated and will not be supported
anymore in Symfony 3.0. Pass the fully-qualified class name of the
type instead.
Before:
$form = $this->createForm(new MyType());
After:
$form = $this->createForm(MyType::class);
Seeing as I can't pass data through with the fully-qualified class name, is there an alternative?
This broke some of our forms as well. I fixed it by passing the custom data through the options resolver.
In your form type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->traitChoices = $options['trait_choices'];
$builder
...
->add('figure_type', ChoiceType::class, [
'choices' => $this->traitChoices,
])
...
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'trait_choices' => null,
]);
}
Then when you create the form in your controller, pass it in as an option instead of in the constructor:
$form = $this->createForm(ProfileEditType::class, $profile, [
'trait_choices' => $traitChoices,
]);
Here's how to pass the data to an embedded form for anyone using Symfony 3. First do exactly what #sekl outlined above and then do the following:
In your primary FormType
Pass the var to the embedded form using 'entry_options'
->add('your_embedded_field', CollectionType::class, array(
'entry_type' => YourEntityType::class,
'entry_options' => array(
'var' => $this->var
)))
In your Embedded FormType
Add the option to the optionsResolver
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Yourbundle\Entity\YourEntity',
'var' => null
));
}
Access the variable in your buildForm function. Remember to set this variable before the builder function. In my case I needed to filter options based on a specific ID.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->var = $options['var'];
$builder
->add('your_field', EntityType::class, array(
'class' => 'YourBundle:YourClass',
'query_builder' => function ($er) {
return $er->createQueryBuilder('u')
->join('u.entity', 'up')
->where('up.id = :var')
->setParameter("var", $this->var);
}))
;
}
Here can be used another approach - inject service for retrieve data.
Describe your form as service (cookbook)
Add protected field and constructor to form class
Use injected object for get any data you need
Example:
services:
app.any.manager:
class: AppBundle\Service\AnyManager
form.my.type:
class: AppBundle\Form\MyType
arguments: ["#app.any.manager"]
tags: [ name: form.type ]
<?php
namespace AppBundle\Form;
use AppBundle\Service\AnyManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyType extends AbstractType {
/**
* #var AnyManager
*/
protected $manager;
/**
* MyType constructor.
* #param AnyManager $manager
*/
public function __construct(AnyManager $manager) {
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$choices = $this->manager->getSomeData();
$builder
->add('type', ChoiceType::class, [
'choices' => $choices
])
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\MyData'
]);
}
}
In case anyone is using a 'createNamedBuilder' or 'createNamed' functions from form.factory service here's the snippet on how to set and save the data using it. You cannot use the 'data' field (leave that null) and you have to set the passed data/entities as $options value.
I also incorporated #sarahg instructions about using setAllowedTypes() and setRequired() options and it seems to work fine but you first need to define field with setDefined()
Also inside the form if you need the data to be set remember to add it to 'data' field.
In Controller I am using getBlockPrefix as getName was deprecated in 2.8/3.0
Controller:
/*
* #var $builder Symfony\Component\Form\FormBuilderInterface
*/
$formTicket = $this->get('form.factory')->createNamed($tasksPerformedForm->getBlockPrefix(), TaskAddToTicket::class, null, array('ticket'=>$ticket) );
Form:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefined('ticket');
$resolver->setRequired('ticket');
$resolver->addAllowedTypes('ticket', Ticket::class);
$resolver->setDefaults(array(
'translation_domain'=>'AcmeForm',
'validation_groups'=>array('validation_group_001'),
'tasks' => null,
'ticket' => null,
));
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->setTicket($options['ticket']);
//This is required to set data inside the form!
$options['data']['ticket']=$options['ticket'];
$builder
->add('ticket', HiddenType::class, array(
'data_class'=>'acme\TicketBundle\Entity\Ticket',
)
)
...
}

Categories