Embedded collection of embedded collection - isValid issue - symfony

Let's say I've got a FooType with BarType (collection; cascade validation setted to true) which itself has a FooBarType (collection; cascade validation setted to true)
So
//FooType
$builder
[...]
->add('bar', 'collection', [
//...
'type' => new BarType()
'error_bubbling' => false,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'by_reference' => false,
]
);
//BarType
$builder
[...]
->add('fooBar', 'collection', [
//...
'type' => new FooBarType()
'error_bubbling' => false,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'by_reference' => false,
]
);
Now the problem is, if I add from GUI one element to fooBar collection that's invalid due to Valid constraints, the error is attached to WHOLE collection (and not to collection's element field) and the element claim to be valid (saw from profiler). If I add from GUI more than one element to fooBar collection is still invalid, invalid status (error) is still attached to collection, first element still claims to be valid but other ones has error attached (that is what I desire).
Question
Why such behavior? Any ideas?
Additional info
This is the invalid path shown in profiler
Object(Symfony\Component\Form\Form).data.bar[0].foobar[0].fieldName =
null
that got the actual value, but is "tied" to collection and not to field element.
Symfony version: 2.7

Solution was pretty "easy" but hard to find.
I asked to myself if it was better to delete the question or to answer here directly. As I'm pretty sure that this error could happen also to other people and "investigation" isn't so easy, I've decided to answer indeed.
Mistake
Mistake was onto collection prototype javascript and its indexes. I'm talking about
http://symfony.com/doc/current/cookbook/form/form_collections.html
addTagForm() javascript function, that in my case was customized in order to reach what I need. The problem was I made a mistake onto index calculation so every new collection was starting from 1 instead of 0.
Thus, when form validation component tried to attach error to right field, producing this
Object(Symfony\Component\Form\Form).data.bar[0].foobar[0].fieldName =
null
no field (at foobar level) with index = 0 was find and error was attached to its parent (the collection).
So when I added more then one element to collection, every error I saw was relative to "other" field (in a n-1 fashion).
How i spot this error
Using profiler > form , onto left column you can spot all form fields with error attached. Observing indexes you can find what I'm talking about.

Related

How do I change the single valued ChoiceType field to multi-value field without affecting existing data?

Currently, I have a Choicetype form field with a single value. But now I want it to be multi-valued. I added multiple => true, for that FormType, but it's giving an error like below:
Uncaught PHP Exception Symfony\Component\Form\Exception\TransformationFailedException: "Unable to transform value for property path "[facebook_lead_id]": Expected an array." at /app/vendor/symfony/form/Form.php line 1087 {"exception":"[object] (Symfony\Component\Form\Exception\TransformationFailedException(code: 0): Unable to transform value for property path "[facebook_lead_id]": Expected an array. at /app/vendor/symfony/form/Form.php:1087, Symfony\Component\Form\Exception\TransformationFailedException(code: 0): Expected an array. at /app/vendor/symfony/form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php:42)"} []
Any idea on how to convert single-valued to multiple values without impacting existing data.
To achieve the multi select you want, you must set the properties multiple and expanded to true.
$builder->add('fruits', ChoiceType::class, [
'multiple' => true,
'expanded' => true
'choices' => [
'bananas' => 1,
'apples' => 2,
'oranges' => false,
],
]);
Details here.

Non-mapped field is valid whilst it is required and empty

I have a field which is non-mapped and required.
$builder->add('termsAndConditions', CheckboxType::class, [
'required' => true,
'mapped' => false,
'attr' => [
'class' => 'c-custom-option',
],
]);
Clientside validation will throw an error when empty, but serverside says it's valid. Currently i do an extra check on form submission $form->isSubmitted() && $form->isValid() && $form->get('termsAndConditions')->getData()==true but the form->isValid() method shouldn't return true in my opinion
As you can see in docs:
If true, an HTML5 required attribute will be rendered. The corresponding label will also render with a required class.
This is superficial and independent from validation. At best, if you let Symfony guess your field type, then the value of this option will be guessed from your validation information.
So, as you can see, it's only about client side validation.

How to create collection of different types of forms?

is it possible to embed dynamically different types of forms to a single collection?
Let say I have a document (using ODM) with some embedded documents. These document I need to take and create a separate form for them. Up to this point it is OK. But how can I put them in the collection type of form, so I can be able to perform auto add and delete action of form?
Or I can do it manually, no problem, but where can I put my code? After calling bindRequest on the form? Is there a chance to remove deleted items from the form, because there is a possibility that I would not pass the isValid method.
And what I have learned so far that there is no way how to modified the form after it is created. I have an abstract form builder to which I'm adding these embedded documents. It is not a collection but separate forms, because each document has it's own form type. Is it correct approach or am I wrong?
Thanks for any correction or advice
It sounds like you would create the forms for all of your embedded documents and then add those as collections to a "parent" form for your document. In that way, your top level form becomes the "collection" which you would then get the embedded data from.
So if your document is A:
class AType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->add('b', 'collection', [
'type' => new BType(),
'allow_add' => true,
'allow_delete' => true,
])->add('c', 'collection', [
'type' => new CType(),
'allow_add' => true,
'allow_delete' => true,
])->add('d', 'collection', [
'type' => new DType(),
'allow_add' => true,
'allow_delete' => true,
]);
}
}
Forms are effectively just interactive views for your data model so this sounds like the way forward unless there's something stopping you from doing it this way?

Validate a Collection Field Type in Symfony 2 with allowExtraFields=true

I'm trying to validate a collection form field:
$builder->add(
'autor',
'collection',
array(
'type' => 'text',
'options' => array('required' => false),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'error_bubbling' => false
)
);
I use JavaScript, as suggested in the Cookbook, to dynamically add more text fields to the collection. My problem is, that I don't know, how to validate these fields. The collection validator lets me validate specific fields of a collection by their name but not simply every field of it. How can I manage that?
Even cooler would be, if I could check, if at least one of the fields is notBlank instead of forcing it to every field.
Best regards
You can use the "constraints" option defined in form Field Type that are available on all fields.(http://symfony.com/doc/master/reference/forms/types/form.html#constraints).
In your case, you can add your constraint like this:
$builder->add('autor', 'collection', array(
'constraints' => new NotBlank()),
));
(in this case dont forget to include the constraints provided by the Validation component:
use Symfony\Component\Validator\Constraints\NotBlank; ...)
i didnt test but i think with this every input will be validate againts the constraint you assigned to the field, and as you have the option "error_bubbling" as false, an error message should be attached to the invalid element.
--EDIT--
Since you even use the 2.0 version of Symfony i think this solution solves your problem, however I strongly advise you to update to the 2.3 version.
You can create a Form Event Subscriber(http://symfony.com/doc/2.0/cookbook/form/dynamic_form_modification.html) that will be listening the POST_BIND event.(note that Post Bind event is Deprecated since version 2.3 and will be removed in 3.0);
In your subscriber class, you will validate each of your submited authors as you want and add an error to the form if something is wrong.
Your postBind method could be something like this:
public function postBind(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
// get the submited values for author
// author is an array
$author = $form['autor']->getData();
// now iterate over the authors and validate what you want
// if you find any error, you can add a error to the form like this:
$form->addError(new FormError('your error message'));
// now as the form have errors it wont pass on the isValid() method
// on your controller. However i think this error wont appear
// next to your invalid author input but as a form error, but with
// this you can unsure that non of the fields will be blank for example.
}
you can check the Symfony2 Form Component API if you have any doubt about a core method.
http://api.symfony.com/2.0/index.html

Symfony 2, how to persist join table entities?

This is such a trivial problem that I can't believe I couldn't find an answer.
Symfony 2, doctrine 2.1. I've got two entities and one intermediate entity (join table). User, Pref, and UsersPrefs. Pref table is dictionary table, so that I could change pref name in one place only. Ok, let's see the picture:
infographic http://dl.dropbox.com/u/22495762/infographic.png
As You can see, I want to have a checkbox group, with all the possible choices (prefs) and preferred choices checked. So, if there are 3 prefs, and only 2 selected by the user, there should be 3 checkboxes, 2 selected.
It's simple, if done plain PHP - query database twice to get list of all prefs and user prefs, render checkboxes depending on values, add some actions to handle form submit, done.
But for the life of God I can't get this to work using symfony & doctrine. I was able to get to the point where I can update relationships in doctrine and further in database, but I'm using raw query values for that:
$data = $request->request->get('some_form');
and this supposedly isn't the way it should be done?
Morevoer, I'm completely stuck as to how should I display checkbox list. I either get list of all options, none checked, or only user options, all checked. Or 'left joined' result set with checkboxes for all cases, useless anyway.
I've come to the point where I tried to overload twig checkbox template, but I couldn't pass variables to the form template, and that was the last straw...
EDIT:
This way I'm getting group of checkboxes, not connected to user choices:
->add('prefs', 'entity', array(
'class' => 'Some\TestBundle\Entity\Pref',
'expanded' => 'true',
'multiple' => 'true',
'property' => 'name'
))
And this way I'm getting only user choices, all checked:
->add('prefs', 'entity', array(
'class' => 'Some\TestBundle\Entity\UserPrefs',
'multiple' => 'false',
'expanded' => 'false',
'property' => 'pref.name',
'query_builder' => function(EntityRepository $er) use ($id) {
return $er->createQueryBuilder('u')
->where("u.user = :id")
->setParameter('id', $id)
;
},
))
And I tried left joins and other options, but at best I could get list of all possible options for all possible users, checked accordingly.
EDIT (POSSIBLE SOLUTION):
I'm displaying checkbox group:
->add('pref_ids', 'choice', array(
'choices' => array(
'1' => 'pref one',
'2' => 'pref two',
'3' => 'pref three',
),
'expanded' => 'true',
'multiple' => 'true'
))
I've added $pref_ids array in User entity. Now I just need to set values in array according to preferences chosen by user:
public function setPrefIds()
{
$prefs = $this->getPrefs();
$this->pref_ids = array();
foreach($prefs as $pref){
array_push($this->pref_ids, $pref->getPref()->getId());
}
return $this;
}
This way I get appropriate checkboxes checked.
Writing values to database is reversal of the process. I'm getting input values from request:
$data = $request->request->get('edit_form');
var_dump($data['pref_ids']);
Removing all user prefs:
foreach ($userPrefs as $pref){
$em->remove($pref);
}
And setting actual associations in doctrine from ids:
$entity->setPrefsById($em, $data['pref_ids']);
Here I'm passing entity manager to entity itself, but I need to refactor it, because it looks kinda messy this way.
Then $em->flush(); and that's it.
That's the best I could come up with. Probably it's overcomplicated and should be done entirely different way. Unfortunately couldn't figure out this "other way".
You need the choice field type: http://symfony.com/doc/current/reference/forms/types/choice.html
In your builder, it will be something like
$builder->add('prefs', 'choice', array('multiple' => true, 'expanded' => true, 'choices' => fetch the available prefs from the database here);
Edit: Sorry I'm mistaken, you need the "Entity" type, which fetches automatically the choices from the database: http://symfony.com/doc/current/reference/forms/types/entity.html
You must still put multiple => true expanded => true to get checkboxes.

Resources