How can I Validate All Children of a Form Collection? - forms

I have spent a long time without making progress on what seems to be a simple problem. I need to allow the user to add up to three references to a form. Each reference will have a name and phone number (two text inputs). I would like to do some simple validation on each one. I have created my ReferenceType and nested it into the main form with the following (note, I created the PhoneNumber validator and added the Null() just for testing):
$builder->add('references', 'collection', array(
'type' => new ReferenceType(),
'by_reference' => false,
'allow_add' => true,
'label' => false,
'required' => false,
'attr' => array('class' => 'reference'),
'options' => array(
'required' => false,
'label' => false,
),
'cascade_validation' => true,
'constraints' => array(
new Null(),
new PhoneNumber(),
)
));
ReferenceType:
$builder->add('name', 'text', array(
'label' => 'Reference',
'required' => false,
'cascade_validation' => true,
'attr' => array(
'placeholder' => 'Name'
)
));
$builder->add('phone', 'text', array(
'label' => false,
'required' => false,
'attr' => array(
'placeholder' => 'Phone Number'
),
'cascade_validation' => true,
'constraints' => array(
new Length(array('max' => 14)),
new PhoneNumber(),
)
));
All of that is quite simple and straight forward. However, I have been unable to get the form to throw an error for any reason related to those references. I use Propel for my ORM and have a lot of validation which works correctly on the main form. These references also display and function correctly except for validation. They are all mapped to one column (references) and I have tried Propel's type="array" as well as defining my own getter and setter which serializes the array of items. The type=array doesn't work at all with the collection, and serializing it seems to work ok.
I have searched SO and symfony.com docs for an answer but have found nothing that would actually cause either name or phone to throw an error. I have tried adding validation to my validation.yml file without success (3 chars only for testing):
references:
- Valid: ~
- Collection:
fields:
name:
- Length:
max: 3
maxMessage: Please limit your reference's name to 3 characters or less.
I'm probably missing something very obvious here, but I can't seem to grasp what. I have also dug through the Profiler and everything appears correct in there as well.
Could any provide hints on things to look for? It seems that creating a new database just for references is a little overkill (they don't need to be searchable or anything). Propel has some information on collections, but it didn't seem to make a difference. Or am I entirely missing the idea here and perhaps I should implement the fields an entirely different way?
Symfony 2.6
Propel 2

Related

choice_translation_domain not working in symfony 2.7

I just upgraded my project to Symfony 2.7. One of the features why I wanted to upgrade as soon as possible, was the choice_translation_domain option for form fields.
But somehow there are still more then 3000 warnings for missing translations.
Here is how I added one field where the translation is already handled by doctrine:
$builder->add('product', 'entity', array(
'class' => 'MyProject:Product',
'required' => false,
'multiple' => false,
'empty_value' => '',
'choice_translation_domain' => false,
'label' => 'label.product',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('product')
->orderBy('product.title', 'ASC');
},
));
In translation debugging, I still see, that Symfony tries to translate the already translated product titles.
My problem was that we overwrite the default form_div_layout from Symfony.
So I had to change our custom form layout .twig file in order to make the option choice_translation_domain work.

zf2 doctrine form int required not working

$inputFilter->add(array(
'name' => 'seatingCapacity',
'required' => TRUE,
'filters' => array(
array('name' => 'Int'),
),
));
In my Doctrine Entity, I have a getInputFilter method which I use for form validation. The above is code snippet for one of the input elements. My problem is the required => true is not working even if I submit an empty form.
After researching I found out, that the Int filter converts the empty input to 0 and submits it and that is why the required validator is not working. I just need reasons why it may not be working.
For reference where I searched
Zend Framework 2 - Integer Form Validation
where he suggests to use Between validator
$inputFilter->add($factory->createInput(array(
'name' => 'zip',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
'validators' => array(
array(
'name' => 'Between',
'options' => array(
'min' => 1,
'max' => 1000,
)
)
)
)
));
I want to know why I should use Between and why required is failing.
You should only use the Between validator to validate if the given value is Between a min and max value. It has nothing to do with solving your empty value to 0 issue.
You should ask yourself 2 questions:
Is 0 a valid value?
Do I want to automatically cast null empty string ('') and other empty values to this 0 or not?
If you actually want to prevent that the Int filter sets an empty value to 0, maybe then you should not use this filter at all then?
You can instead add an IsInt validator to check if the given value is an integer. Your required => true setting will work as expected and validation will fail on any other (not integer) input so also on null, empty strings etc.
Thank you for your time, energy & effort.
Actually I solved the problem.
I had copy-pasted the code, which I should have been careful while doing so.
What I found out is there is no such filter named 'Int' and actually it's 'Digits'
$inputFilter->add(array(
'name' => 'seatingCapacity',
'required' => TRUE,
'filters' => array(
array('name' => 'Int'),
),
));
shows me error because the filter name should have been 'Digits'.
Only doing so solved my problem and works as per my requirement.
$inputFilter->add(array(
'name' => 'seatingCapacity',
'required' => TRUE,
'filters' => array(
array('name' => 'Digits'),
),
));
This is the right way to do and I would advice you to be careful while referring code from your sources because it's an illogical mistake I did.
Happy coding

zend addElements form onchange select

I try to add dinamically a form input Elemnt on onchange of select element.
I have this code:
$this->addElement('select', 'nationality', array(
'multiOptions' => array('CH' => 'Choose','IT' => 'Italy', 'other' => 'Other'),
'required' => true,
'label' => 'Nation',
'filters' => array('StringTrim'),
'onChange' => '???'
));
I try to paste my new element instead "???"
$this->addElement('select', 'nationality', array(
'multiOptions' => array('CH' => 'Choose','IT' => 'Italy', 'other' => 'Other'),
'required' => true,
'label' => 'Nation',
'filters' => array('StringTrim'),
'onChange' => '$this->addElement(
'text', 'codice_fiscale', array(
'required' => true,
'label' => 'Codice fiscale',
'required' => true
))'
));
I know is not correct, but I dont' found any documentation about it.
How can I add an element onchange select value?
Thanks in advance
I would approach the problem in a different way, if it's possible for you:
javascript functions to call on the onChange. It would be syntax correct (and, as you said, what you wrote is not correct, it's going to be printed out entirely on the onChange attribute, not interpreted through php).
so, i would write
'onChange' => 'addElement()'
and then declare the javascript function which handles new elements. Or, you can prepare the elements before (if you have a fixed number), and show / hide them. It may be convenient if you don't have much elements, but if they're too many, just add through javascript the needed one.
PseudoCode Javascript
function addElement()
{
// add your element here, or show / hide it
}
As far as I saw from my experience and similar cases
(Zend_form_element_select onchange in zend framework,
Zend Form: onchange select load another view content)
you won't escape from Javascript if you want to handle the "onChange"

How do I allow html tags in label for Zend form element using addElement()?

I am brand new to Zend and I've been given a project to make adjustments on. I'd like to add html to the labels for my form elements but I can't seem to get it right.
Here's what I have:
$this->addElement('text', 'school_name', array(
'filters' => array('StringTrim'),
'validators' => array(
array('StringLength', false, array(0, 150)),
),
'required' => true,
'label' => 'Name* :<img src="picture.png">,
'size' => '90',
));
As is, of course, the <img src="picture.png"> text gets escaped and the whole string is displayed.
I've read that I need to use 'escape' => false in some capacity but I can't figure out where/how to use it in my specific case.
Any help would be great. Thanks!
After calling addElement fetch the label's decorator and change the escape setting:
$form->getElement('school_name')->getDecorator('label')->setOption('escape', false);
If you use this type of label a lot, you should consider writing a custom decorator.
You can also use the disable_html_escape in 'label_options' when adding an element to the form:
$this->add(array(
....
'options' => array(
'label' => '<span class="required">Name</span>,
'label_options' => array(
'disable_html_escape' => true,
)
),
...
));
Credit to Théo Bouveret's post 'Button content in ZF2 forms' for the answer.

Remove null values coming from empty collection form item

I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f