Allow multiple date formats in symfony2 form builder - symfony

A client requested this. He wants to allow multiple date formats for a birthday field. The documentation didn't give me any clues how to realize it and neither did google.
Anyone who experienced such a request before and has a lead how to achieve this?
Currently it looks like this:
$builder->add('xxx', 'birthday', array('widget' => 'single_text', 'invalid_message' => 'some message (dd-MM-yyyy)', 'format' => 'dd-MM-yyyy'))

If you want to handle different date formats in your form, you ought to look at DataTransformer
It helps you to transform data from one format to another, for example:
2013-03-26 ==transform==> 2013/03/26
2013.03.26 ==transform==> 2013/03/26
26.03.2013 ==transform==> 2013/03/26

Data transformer should NOT be used in this case.
The main point of data transformer is to convert a data back and forth between view <=> norm <=> model.
It's not your case, you don't want a bidirectional transformation.
What you want is to filter the data, and for that, you can use a form event:
$builder->add(
$builder
->create('xxx', 'birthday', [...])
->addEventListener(
FormEvents::PRE_SUBMIT,
function(FormEvent $event) {
$value = $event->getData();
// Do the filtering you want (for example replacement of special chars with a dash)
// $value = preg_replace('[^\d]', '-', $value)
$event->setData($value);
}
)
);
And you are done, the data is filtered on the PRE_SUBMIT event, before validation.
I wrote this example from memory and didn't tested it, maybe you'll should adapt it (and add your field option instead of [...].

Related

Symfony: Index in collection Type

I am trying to set the label of an embedded form type (collection type).
These labels have to be different for the different collections.
For example I have two collections and an array with the labels ['label1','label2'] in die form type.
I think I need the index of the collection iteration to get the right label.
Like below i need the index for the entry_options label to get the right value of the $labels array.
Thats my buildForm Method
public function buildForm(FormBuilderInterface $builder, array $options)
{
$labels = ['label1', 'label2'];
$builder
->add('otherForms', CollectionType::class, [
'entry_type' => otherFormType::class,
'entry_options' => ['label' => $labels[$index]],
])
...
;
}
After reading your comments, I must agree that translation bundles are sometimes heavy weight and quite inconvenient.
The CollectionType usually ignores the index/key of collection items given to it, though.
Now I see two scenarios: you want to give the labels as an option to your form or the form shall be dependent on the data (using form events). Your code so far suggests, that languages are set globally and will not be added on the fly in some other manner.
In the first scenario it's very simple (see below), the second scenario requires some event listener in your form, but apart from that, it's quite similar.
The essential idea is to just create the forms you need (optionally inside the pre-submit handler), since you apparently already know which forms you need from the label array:
foreach($labels as $index => $label) {
$builder->add('otherForms'.$index, otherFormType::class, [
'label' => $label,
'property_path' => 'otherForms['.$index.']',
]);
}
The use of property_path will help you access the proper elements (however, changing the language (= key) of a translation might be difficult, but that should not happen, usually. And even if it happens, only some copy-pasta is necessary).
Your otherForms keys probably will be differently, but I guess you'll figure it out.

Populate 3 selection lists using queries on the same entity, using Symfony2 buildForm()

I have an entity Machine which has a relation MM with other entity Piece. The pieces can be from 3 different types. Currently the form Machine is built with a selection list in which the whole array collection Machine.pieces is fetched. My idea is to build 3 different selection lists with a subset of Machine.pieces each.
I have tried two different approaches but I have no been able to accomplish it.
Use a MachineRepository class where a method
public function findPiecesByPieceType($pieceTypeID)
returns the proper query->getResult().
Then I add a choiceType in MachineType but I am not able to populate it from MachineController. I have used $form->get('pieces')->setData($arrcollectPieces) and other methods to add choices but I always get error.
How could I add choices from the controller to a Form?
In the form I use a queryBuilder
->add('pieces', EntityType::class, array(
'label' => 'label_pieces',
'class' => 'AppBundle\Entity\Piece',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->where('p.pieceType = :pieceType')
->setParameter('pieceType', 1);
},
)
)
this works but when I try to add more queryBuilders (->add('pieces2'... and so on) I have the error because
Neither the property pieces2 nor one of the methods getPiecess2(), pieces2(), isPieces2(), hasPieces2(), __get() exist and have public access in class AppBundle\Entity\Machine.
How can I use the various queryBuilders not bounded to a method name in that way?
Maybe both approaches are incorrect and I should solve this in a different way?
(Posted on behalf of the OP).
How to make it using 1.
In MachineController forget setData(), instead turn the arrayCollection into 2 arrays (arKeys, arValues) and send them to the form as the 3rd parameter in createForm().
$form = $this->createForm(<type>, <data>,
array ('p_keys' => array(...), 'p_values' => array(...)));
From MachineType.ConfigureOptions() we can fetch them
$resolver->setDefined(["p_keys",'p_values']);
and they will be available in MachineType.buildForm()
$options['p_keys'];
$options['p_values'];

Form tests: How to submit a collection to an existing form? [duplicate]

This question already has answers here:
Symfony2: Test on ArrayCollection gives "Unreachable field"
(4 answers)
Closed 6 years ago.
I use two ways to test my forms:
By using $form = …->form();
Then setting the values of the $form array (more precisely this is a \Symfony\Component\DomCrawler\Form object):
Full example from the documentation:
$form = $crawler->selectButton('submit')->form();
// set some values
$form['name'] = 'Lucas';
$form['form_name[subject]'] = 'Hey there!';
// submit the form
$crawler = $client->submit($form);
By sending the POST data directly:
The previous code doesn't work with forms which manage collections (relying on fields created by Javascript) because it throws an error if the field doesn't exist. That's why I also use this other way.
Full example from the documentation:
// Directly submit a form (but using the Crawler is easier!)
$client->request('POST', '/submit', array('name' => 'Fabien'));
This solution is the only way I know to test forms which manage collections with fields added by Javascript (see link to documentation above). But this second solution is harder to use because:
it doesn't check which fields exist, this is impractical when I have to submit a form with existing fields and a collection which relies on fields created dynamically with Javascript
it requires to add the form _token manually
My question
Is it possible to use the syntax from the first way to define the existing fields then add new dynamically created fields with the second syntax?
In other words, I would like to have something like this:
$form = $crawler->selectButton('submit')->form();
// set some values for the existing fields
$form['name'] = 'Lucas';
$form['form_name[subject]'] = 'Hey there!';
// submit the form with additional data
$crawler = $client->submit($form, array('name' => 'Fabien'));
But I get this error:
Unreachable field "name"
And $form->get('name')->setData('Fabien'); triggers the same error.
This example is not perfect because the form has no collection, but it's enough to show you my problem.
I'm looking for a way to avoid this validation when I add some fields to the existing form.
This can be done by calling slightly modified code from the submit() method:
// Get the form.
$form = $crawler->filter('button')->form();
// Merge existing values with new values.
$values = array_merge_recursive(
$form->getPhpValues(),
array(
// New values.
'FORM_NAME' => array(
'COLLECTION_NAME' => array(
array(
'FIELD_NAME_1' => 'a',
'FIELD_NAME_2' => '1',
)
)
)
)
);
// Submit the form with the existing and new values.
$crawler = $this->client->request($form->getMethod(), $form->getUri(), $values,
$form->getPhpFiles());
The array with the news values in this example correspond to a form where you have a fields with these names:
<input type="…" name="FORM_NAME[COLLECTION_NAME][A_NUMBER][FIELD_NAME_1]" />
<input type="…" name="FORM_NAME[COLLECTION_NAME][A_NUMBER][FIELD_NAME_2]" />
The number (index) of the fields is irrelevant, PHP will merge the arrays and submit the data, Symfony will transform this data in the corresponding fields.

Symfony2: Using two form fields for one entity property?

This is an existing schema I'm working with and I'm trying to not make any changes for the time being. I have an entity property that represents a university semester, like "fall12", "spring11", etc.
When adding or editing this entity with a form, I want to split that property into two form fields: "Season" (fall or spring") and "Year" (2011, 2012, etc):
...
->add('formSemesterSeason', 'choice', array(
'label' => 'Season',
'mapped' => false,
'choices' => array('fall' => 'Fall', 'spring' => 'Spring'),
))
->add('formSemesterYear', 'choice', array(
'label' => 'Year',
'mapped' => false,
'choices' => $this->courseManager->getYearChoices(),
))
...
When submitting the form, the values need to be combined and saved to the "semester" property on the entity as a string
When viewing the edit form, the existing "semester" value needs to be split between the two.
I don't think data transformers help here, since they apply to transforming just one form item.
Right now I'm using a form event POST_SET_DATA to fill out the two form fields when editing an existing entity:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($course_manager) {
$course = $event->getData();
$form = $event->getForm();
$semester = $course->getSemester();
$parsed_semester = $course_manager->parseSemesterMachineName($semester);
$form->get('formSemesterSeason')->setData($parsed_semester->season);
$form->get('formSemesterYear')->setData($parsed_semester->yearFull);
});
This works well, but how do I then combine the values back after the form has been submitted? I can do it easily in the controller, but I think I should be able to use form events, and have the data manipulation take place before form validation.
You can combine them back in a POST_SUBMIT listener.
The best way (reuseable) would be to create your own custom form type with a data transformer to split/combine the fields internally.
There are "recipes" in the cookbook but the best way that I found to create it was to rip apart the DateTime field type and associated transformers (DataTransformerChain, DateTimeToArrayTransformer & ArrayToPartsTransformer) for parts and build my own from that.

Drupal form validation functions

Is there anyway say Drupal to validate form elements like email fields, passwords, numeric fields validate automatically lets say bind a system validator
$form['email] = array(
'#title' => t('Email'),
'#type' => 'textfield',
'#validate_as' => array('email', ...),
...
);
To validate a numeric field in Drupal use:
'#element_validate' => array('element_validate_number')
No need to create a custom validation function.
http://api.drupal.org/api/drupal/includes%21form.inc/function/element_validate_number/7
Rimian is both correct and wrong.
The good thing as Rimian points out, is that you can attach any validation function to your form fields using the #element_validate.
However I'm not aware of a set of form api validation functions you can call to test most common things like, if the value is:
a int
a positive int
a valid date (such a function exists in the date module though)
a email-address (you can use valid_email_address to check the email, but you need a function to raise validation error)
So while you can do this, it's a bit more work than you were hoping for, as you will need to create these validation functions yourself. But once you have done this, you can reuse them with #element_validate.
The use of #element_validate is mostly centered around complex validation fx date validation, location validation and such, as it requires some work to create these validation functions. Most of the time, you don't need to validate that many numbers etc (which you quite easily could do within a normal validation function using a loop). So I'm not sure how much this will be of help to you, but it's definitely a possibility.
The Form API Validation module does exactly what you request: http://drupal.org/project/fapi_validation
For client-side validation there is also http://drupal.org/project/clientside_validation (which can use rules provided by Form API Validation).
Yep!
Though I have not experimented with it much.
http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/6#element_validate
$form = array(
'#type' => 'fieldset',
'#title' => t('Input format'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => $weight,
'#element_validate' => array('filter_form_validate'),
);

Resources