I have a problem with forms' error bubbling.
One field in my form is defined like this:
$formBuilder->add('title','text',
'required' => true,
'error_bubbling' => false,
)
)
I would like to add a validator like this to this field:
/**
* #Assert\True(message = "Bad title.")
*/
public function getTitleCorrect()
{
/* ... */
return false;
}
It works ok, but the error message shows up on top of the form, not in the field row.
In the Twig template this error message is rendered by {{form_errors(form)}} as a global error. When I use {{form_errors(form.title)}}, it does not print my error.
What can I do to change the assignment of the error?
Messages are attached to a field only when validator is attached to corresponding property.
Your validator is attached to a method of the class so error is indeed global.
You should to something like that:
use ...\TitleValidator as AssertTitleValid;
class MyEntity
{
/**
* #AssertTitleValid
*/
private $title;
}
And create your own TitleValidator class.
'error_bubbling' is false by default unless it is set to true or form is compound.
Besides error might be bubbled up if there is some mismatch between form field and validated property/method, like you have in your example or if different case is used (snake_case for form field and $camelCase for validated property).
In this case you may use 'error_mapping' in your form class:
/**
* #param OptionsResolver $resolver
*
* #throws AccessException
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => YourEntity::class,
'error_mapping' => [
'entityProperty' => 'form_field_1',
'entityMethod' => 'form_field_2',
],
]);
}
Related
I'm trying to submit array with values to a symfony 4 form field but the validation keeps failing.
I'm in the process of updating my application from symfony 2.7 to symfony 4. The problem is that a form that I used to use now always fails validation due to changes in symfony forms.
The symfony form has the following field
$builder->add('contactData', null, ['mapped' => false])
In symfony 2.7 I would always submit a POST request with array values in the contactData field and since it's not mapped it would just set the data to the field object in the submit process and the values were accessed in the Handler. Example request:
{
"name": {
"aField": "aValue",
"contactData": {
"something": "value"
}
}
}
However in symfony 4 there is now an added validation check in the \Symfony\Component\Form\Form class
} elseif (\is_array($submittedData) && !$this->config->getCompound() && !$this->config->hasOption('multiple')) {
that causes the validation to fail when submiting data to the contactData field, since the submittedData is indeed an array. I've been looking all over the internet and reading through the documentation of symfony but I can't seem to find a way to induce the same behavior as in symfony 2.7.
I would much appreciate any advice, I've been stuck on this for a while
Symfony has changed from v2.7 to 4.0, there is a lot of default values changed;
I faced the same problem and after 2 hours of investigations,
I ended up adding the attributes compound and allow_extra_field.
So, this should solve your problem:
$builder->add('contactData', null, [
'mapped' => false,
'compound' => true,
'allow_extra_fields' => true,
])
EDIT:
This didn't work as expected, I ended up with no error and no content as a submitted data, so I created a new type to add fields dynamically on a pre-submit event as following:
UnstructuredType.php
<?php
namespace ASTechSolutions\Bundle\DynamicFormBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
/**
* Class UnstructuredType.
*
* This class is created to resolve the change of form's behaviour introduced in https://github.com/symfony/symfony/pull/29307
* From v3.4.21, v4.1.10 and v 4.2.2, Symfony requires defining fields and don't accept arrays on a TextType for ex.
* TODO: this is a temporary solution and needs refactoring by declaring explicitly what fields we define, and then accept on requests
*
*/
class UnstructuredType extends AbstractType
{
/**
* {#inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$this->addChildren($event->getForm(), $event->getData());
});
}
/**
* #param FormInterface $form
* #param $data
*/
public function addChildren(FormInterface $form, $data)
{
if (is_array($data)) {
foreach ($data as $name => $value) {
if (!is_array($value)) {
$form->add($name);
} else {
$form->add($name, null, [
'compound' => true
]);
$this->addChildren($form->get($name), $value);
}
}
} else {
$form->add($data, null, [
'compound' => false,
]);
}
}
}
No need #sym183461's UnstructuredType in the other answer.
The information is in the extra fields.
You define the field like #sym183461 said:
$builder->add('contactData', null, [
'mapped' => false,
'compound' => true,
'allow_extra_fields' => true,
])
And then you can do this:
$contactData = $form->get('contactData')->getExtraFields()
All your data is in there, and it works with deep structures just fine.
I have a form that contain 2 dates: start date(datedebut) and end date(datefin).
I want the end date to be always after the start date. How can i do that?
My form type:
class ReservationType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('datedebut',DateType::class,array(
'widget' => 'choice',
'years' => range(date('Y'), date('Y')+20),
))
->add('datefin',DateType::class,array(
'widget' => 'choice',
'years' => range(date('Y'), date('Y')+20),
))
->add('nbplaces')
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bridge\TravelBundle\Entity\Reservation'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'Bridge_TravelBundle_Reservation';
}
}
You can use a Callback Validator for this. Injected into that callback is a ExecutionContextInterface by which you can access the form, and thus other form params.
Here's an example:
use Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
// …
$builder
->add('start', 'datetime',
'constraints' => [
new Constraints\NotBlank(),
new Constraints\DateTime(),
],
])
->add('stop', 'datetime', [
'constraints' => [
new Constraints\NotBlank(),
new Constraints\DateTime(),
new Constraints\Callback(function($object, ExecutionContextInterface $context) {
$start = $context->getRoot()->getData()['start'];
$stop = $object;
if (is_a($start, \DateTime::class) && is_a($stop, \DateTime::class)) {
if ($stop->format('U') - $start->format('U') < 0) {
$context
->buildViolation('Stop must be after start')
->addViolation();
}
}
}),
],
]);
Usually these kind of tasks are solved by adding validation constraints to check if value of one field is greater then the other. Implement callback validation constraint as stated in the documentation: http://symfony.com/doc/current/reference/constraints/Callback.html You can also create your custom class constraint validator and place validation logic there: http://symfony.com/doc/current/validation/custom_constraint.html
This way whenever a user tries to submit value of datefin which is less than selected value of datedebut he will see a validation error and the form will not be processed.
After that you can always add some javascript code that will filter available dates in datefin field after value in datedebut field is changed.
Also you can use dynamic form modification to render the second date field (and filter its available dates on server side) only if value of the first one is submitted. Check this out: http://symfony.com/doc/current/form/dynamic_form_modification.html
I use symfony validator assert, in my class subscription i have:
/**
* #var string
* #Assert\NotBlank(message="asasass")
* */
private $name;
Forms:
class SubscriptionType extends AbstractType{
public function getName() {
return 'Piotrek_subscription';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'label' => 'Imię i nazwisko'
))
->add('email', 'text', array(
))
->add('save', 'submit', array(
'label' => 'Zapisz się'
)); }
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Piotrek\WitajBundle\Entity\Subscription'
));
}
}
And my controler:
$Subscription = new Subscription();
$form = $this->createForm(new SubscriptionType(), $Subscription);
return array(
'form' => $form->createView(),
My form don't read the custom validate in comment. Imposes standard , but also do not know where. When I delete comment the ruler is still the same.
So how do you change the text validation?
You constraint is a server side one, that means when you submit your form the page reloads and you will get the errors. But the message you see is the default HTML5 error message when you try to submit an empty field, because unless you mention otherwise inside your formbuiler, all fields will be rendered as required that's why you get the default hhtml 5 error message
You can do what want using:
Method 1: this will display your custom message "asasasas" after loading the page
set the required option as false
builder->add('name', 'text',array('required' => false )
Method 2:
Change the default htm5 error message from inside your SubscriptionType , something like this: ( I don't remember it exactly but this could point you to the right path)
builder->add('name' ,'text',array(
'attr'=>array('oninvalid'=>"setCustomValidity('bla bla.. ')")
Hope this works for you.
It looks like symfony does not read annotations in your code.
Probably you have not enabled annotations in app/config.yml
framework:
validation: { enabled: true, enable_annotations: true }
I'm using the LexikFormFilterBundle (current dev-version) with Symfony 2.7.0-BETA1. It works as expected, but…
The Problem
all filter_entity fields throw form errors, when the empty option is submitted (i.e. no entity is selected). It is documented in the basic example, that one has to set
'validation_groups' => array('filtering') // avoid NotBlank() constraint-related message
But in my case, the ManyToOne Entities don't use a NotBlank()-Assert. Despite this, the form throws errors about non existant entities ''. When I select an entity, the filter filters correctly and no error message appears. Other filter field types also don't throw errors, when they are empty.
Here is a stripped down representation of my code:
Entity code:
<?php
// src/AppBundle/Entity/Serviceevent.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Serviceevent
*
* #ORM\Table(name="serviceevents")
* #ORM\Entity(repositoryClass="AppBundle\Entity\ServiceeventRepository")
*/
class Serviceevent
{
/**
* #var \AppBundle\Entity\Park
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Park", inversedBy="serviceevents")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="park_id", referencedColumnName="id", nullable=false)
* })
*/
private $park;
}
Form filter code:
<?php
// /src/AppBundle/Filter/ServiceeventFilterType.php
namespace AppBundle\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ServiceeventFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('park', 'filter_entity', array(
'class' => 'AppBundle:Park',
'property' => 'identifyingName',
'label' => 'Park',
'placeholder' => '',
'required' => false,
));
;
}
public function getName()
{
return 'serviceevent_filter';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false,
'validation_groups' => array('filtering'), // avoid NotBlank() constraint-related message
));
}
}
What I expect
I expect, that with no value selected, no filtering for that field happend without an error thrown.
What I get
Instead, I get form errors for all entity-fields like this one:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[park] =
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "[park]": The choice "" does not exist or is not unique
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
The choice "" does not exist or is not unique
Question is
The question is: how do I get rid of those errors?
User wcluijt's fix in https://github.com/symfony/symfony/issues/14393#issuecomment-94996862 fixed this for me. This confirms, that my problem here was provoked by a Bug in Symfony 2.7.0-BETA1. So wo can close this question as fixed. Sorry for wasting your time with a beta-related bug.
I have a form which contains three objects:
$builder
->add('customer', new CustomerType())
->add('shippingAddress', new AddressType())
->add('billingAddress', new AddressType())
->add('sameAsShipping', 'checkbox', ['mapped' => false])
;
Each of the embedded forms has their own validation constraints and they work. In my main form, I have cascade_validation => true so that all of the embedded form validation constraints are applied. This also works.
I am having trouble 'disabling' the validation on the billingAddress form if the sameAsShipping checkbox is enabled. I can't make the validation in the AddressType form conditional because it always needs to be enforced for the shippingAddress form.
I've solved this same problem by using validation groups.
First, this is important: use the validation_groups option in your AddressType to set the validation groups of every constraint of each field in the type:
<?php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Form\FormBuilderInterface;
class AddressType extends \Symfony\Component\Form\AbstractType
{
function buildForm(FormBuilderInterface $builder, array $options)
{
$groups = $options['validation_groups'];
$builder->add('firstName', 'text', ['constraints' => new Assert\NotBlank(['groups' => $groups])]);
$builder->add('lastName', 'text', ['constraints' => new Assert\NotBlank(['groups' => $groups])]);
}
}
Then, in the parent form pass different validation groups to the two fields:
<?php
$formBuilder = $this->get('form.factory')
->createNamedBuilder('checkout', 'form', null, [
'cascade_validation' => true,
])
->add('billingAddress', 'address', [
'validation_groups' => 'billingAddress'
])
->add('shippingAddress', 'address', [
'validation_groups' => 'shippingAddress'
]);
Then, determine determine your validation groups by looking at the value of the checkbox.
if ($request->request->get('sameAsShipping')) {
$checkoutValidationGroups = ['Default', 'billingAddress'];
} else {
$checkoutValidationGroups = ['Default', 'billingAddress', 'shippingAddress'];
}
You can then validate only either the billingAddress or the shippingAddress, or both using the validation group mechanism.
I chose to use a button:
$formBuilder->add('submitButton', 'submit', ['validation_groups' => $checkoutValidationGroups]);
Create a form model (I use it in nearly every form, but this code here is not tested):
/**
* #Assert\GroupSequenceProvider()
*/
class YourForm implements GroupSequenceProviderInterface {
/**
* #Assert\Valid()
*/
private $customer;
/**
* #Assert\Valid()
*/
private $shippingAddress;
/**
* #Assert\Valid(groups={'BillingAddressRequired'})
*/
private $billingAddress;
private $billingSameAsShipping;
public function getGroupSequence() {
$groups = ['YourForm'];
if(!$this->billingSameAsShipping) {
$groups[] = 'BillingAddressRequired';
}
return $groups;
}
}
Try to use meaningful names. sameAsShipping is hard to understand. Read the if-condition in getGroupSequence: if not billing (address) same as shipping (address) then billing address required.
That's all, clear code in my opinion.