Symfony generate several forms with a twig loop - forms

Can anybody tell me why this code is not working, I do not find an example of this on internet.
{% for dp in dpList %}
<tr>
<td> //values </td>
<td>....</td>
...
<td>
{{ form_start(display) }}
{{ form_row(display.info, { value: dp.info }) }}
{{ form_rest(display) }}
{{ form_end(display) }}
</td>
</tr>
{% endfor %}
What I am trying to do is to show a button "Display" for each row which contains a hidden value of this row. It actually only shows the button into the first row.
Thanks!

I found a solution that suits my needs:
I create a form in a private function:
private function formShow()
{
$show = $this->createFormBuilder()
->setAction($this->generateUrl('admin_platform_show'))
->setMethod('POST')
->add('persno', 'hidden')
->add('Show', 'submit')
->getForm();
return $show;
}
Then in my controller I create an arrayList with a loop on each row I will have, so it will generate as much as forms that I need:
foreach ($idmList as $idm)
{
$show = $this->formShow()->createView();
$arrayForms[] = $show;
}
Then I send my arrayList to my view and access each form like this:
{{ form_start(arrayForms[loop.index0]) }}
{{ form_row(arrayForms[loop.index0].persno, { value: idm.persno }) }}
{{ form_rest(arrayForms[loop.index0]) }}
{{ form_end(arrayForms[loop.index0]) }}
That way I use a secured form made by symfony, containing a specific value of the row that I will reuse in another controller if I press the submit button.
Hope that helps somebody.

This is incorrect. A form object is a single instance of the form, you can't render it multiple times on the same page.
You should either have multiple form objects, or use something else instead of forms, like a link or a button with javascript.
I suggest using jQuery and the post() function.

Related

Symfony3 Render multiple time same form

I would like to render the same form multiple times to handle the same action for two different tabs.
The problem is that when I try, only the form of the first tab is shown, event if I change the id and name of the form.
I found out it's the expected behavior of symfony, but I still need it to work.
I found that it may works with a collection but don't get how it would work.
twig:
{{ form(contactForm, {'attr': {'id': 'contactFormId' ~ Client.Id}, 'name': "contactFormName" ~ Client.Id})}}
Form:
$this->contactForm = $this->createFormBuilder($contact, array('allow_extra_fields' =>true))
->add('Nom', TextType::class, array('mapped'=>false))
->add('Prenom', TextType::class, array('mapped'=>false))
->add('Telephone', TextType::class, array(
'label' => 'Téléphone'))
->add('Email', TextType::class)
->add('Ajouter', SubmitType::class)
->getForm();
It is an older question, but I just came across it facing a similar situation. I wanted to have multiple versions of one form object in a list view. For me the solution was to move the createView() call on the form object to the view instead of calling it in the controller. This is kind of a dirty solution regarding separation of concerns, but I thought to post it so it may help others anyway.
My controller action looks like this:
/**
* #Route("", name="cart_show")
* #Method("GET")
*/
public function showAction(Request $request)
{
/** #var CartInterface $cart */
$cart = $this->get('rodacker.cart');
$deleteForm = $this->createDeleteForm();
return $this->render(
'AppBundle:Cart:show.html.twig',
['cart' => $cart, 'deleteForm' => $deleteForm]
);
// ...
private function createDeleteForm()
{
return $this->createForm(
OrderItemDeleteType::class,
null,
[
'action' => $this->generateUrl('cart_remove_item'),
'method' => 'DELETE',
]
);
}
}
and in the view I set the form variable by calling the createView function on the form variable (deleteForm) passed from the controller:
{% for item in items %}
{% set form = deleteForm.createView %}
{{ form_start(form) }}
{{ form_widget(form.item, {'value': item.image.filename}) }}
<button type="submit" class="btn btn-xs btn-danger" title="Artikel entfernen">
<i class="fa fa-trash-o"></i> entfernen
</button>
{{ form_end(form) }}
{% endfor %}
Once you render a Symfony form, the same form will not render again.
I would suggest creating a form class and calling Controller::createForm() multiple times to create the desired amount of Form instances; you can call isSubmitted etc. on all forms independently.
http://symfony.com/doc/current/book/forms.html#creating-form-classes

Symfony - Add text in generated form

I'd like to do something quite simple, but I can't figure out how to manage it. I have a form:
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
There are several text field in it. I'd like to "insert" some text (like <p>my Text</p>) between two text fields (let's say between name text field and description text field). Form are generated with form builder tool. I've tried something like:
$builder
->add('stuffName') // works well (text field 1)
->add('addedText', 'text', array('label' => 'my Text')) // Trouble here!
->add('stuffDescription'); // works well (text field 2)
But it generates a text field (and not a simple text). I don't care if the text is set in the form builder or directly in twig template... As long as it is between my two text fields. Any idea?
Thanks a lot!
Symfony forms contain only form fields. Any additional content you want has to be added by the template.
This means you'll have to output the form field-by-field. Your form, for example might look like this:
{{ form_start(form) }}
{{ form_row(form.stuffName) }}
<p>Your Text</p>
{{ form_row(form.stuffDescription) }}
{{ form_end(form) }}
For more more information on how you can customize form rendering, please see the forms chapter in the Symfony documentation.
The keyword in this question is generated.
Let's assume, that you build a form generator in Symfony. You have entities like Form, Fields and Fields Items (it's options for select box or buttons for radio button field).
So you have this entities and you create a service to create a form from the data. In the service you build the form ($this->buildedForm - generated form, $page->getFormData() - put the data to the constructed form):
$this->buildedForm = $this->formFactory->create(
'form',
$page->getFormData(),
['action' => '/page/formview/' . $task->getId()]
);
foreach($fields as $field) {
$fieldBuilderMethod = 'construct' . ucfirst($field->getType()) . 'Field';
if (method_exists($this, $fieldBuilderMethod)) {
$this->$fieldBuilderMethod($field);
}
}
return $this->buildedForm;
And you have methods for each type like (examples for Symfony 2):
private function constructInputField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'text',
[
'label' => $field->getName(),
]
);
}
private function constructTextareaField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'textarea',
[
'label' => $field->getName(),
]
);
}
You can now create your custom form type to paste a text in the generated form (it could be placed in the form folder of your bundle and retrieved with namespace "use"):
private function constructSimpletextField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
new SimpletextType(),
[
'label' => $field->getName(),
'data' => $field->getPlaceholder(),
]
);
}
What in this custom field?
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'simpletext';
}
}
And the whole magic comes out in the template. For your custom form type you need to make a custom theme (see https://symfony.com/doc/2.7/form/form_customization.html#form-customization-form-themes). And there:
{% block simpletext_label %}{% endblock %}
{% block simpletext_widget %}
<p>{{ form.vars.data }}</p>
{% endblock %}
{% block simpletext_errors %}{% endblock %}
See, no label, no errors (it just a text) and only text in the field widget. Very handy for generated forms with dynamic template.
EDIT - Symfony 5
In Symfony 5, this solution became simplier. The form customization doesn't changes, and the php code became like this:
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getBlockPrefix(): string
{
return 'simpletext';
}
}
It's used like this :
public function buildForm(FormBuilderInterface $builder, array $options): void {
/* … */
$builder->add('anykey', SimpleTextType::class, [
'data' => "Type your text here",
]);
/* … */
}
Here a sample code which would be self explain
{{ form_start(form, { 'attr': { 'class': 'form-horizontal form-bordered'} }) }}
<div class="form-group">
<div class="col-md-3 ">
{{ form_label(form.User, 'Label text', { 'attr': {'class': 'control-label'} }) }}
</div>
<p>You are free to add whatever you want here</p>
<div class="col-md-9">
{{ form_widget(form.User, { 'attr': {'class': 'form-control'} }) }}
</div>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
In any case, the symfony documentation is pretty clear and well-explain about this point.

modify form action with symfony2 and phpunit

I'm currently working with Symfony2 and I'm testing my project with PHPUnit.
I want to test an exception when a form is submitted with the wrong parameters or the URL isn't complete.
I went trough the documentation of Symfony2 and PHPUnit but didn't find any class/method to do so.
How can I change the value of a form's action? I want to use PHPUnit so the report created is up to date and I can see the coverage of my code.
EDIT:
To clarify my question, some new content.
How do I test the line starting with '>' in my controller? (throw $this->createNotFoundException('Unable to find ParkingZone entity.');)
When the user modifies the action link, in the controller the process will go trough the exception (or error message, if this action is chosen). How can I test this case?
Controller
/**
* Edits an existing ParkingZone entity.
*
* #Route("/{id}/update", name="parkingzone_update")
* #Method("post")
* #Template("RatpGarageL1Bundle:ParkingZone:edit.html.twig")
*/
public function updateAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('RatpGarageL1Bundle:ParkingZone')->find($id);
if (!$entity) {
> throw $this->createNotFoundException('Unable to find ParkingZone entity.');
}
$editForm = $this->createForm(new ParkingZoneType(), $entity);
$deleteForm = $this->createDeleteForm($id);
$request = $this->getRequest();
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('parkingzone_edit', array('id' => $id)));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
}
View:
<form action="{{ path('parkingzone_update', { 'id': entity.id }) }}" method="post" {{ form_enctype(form) }}>
<div class="control-group">
{{ form_label(form.name, 'Name', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.name, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.name) }}</span>
</div>
</div>
<div class="control-group">
{{ form_label(form.orderSequence, 'Rang', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.orderSequence, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.orderSequence) }}</span>
</div>
</div>
<div class="control-group">
{{ form_label(form.image, 'Image', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.image, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.image) }}</span>
</div>
</div>
{{ form_rest(form) }}
<div class="form-actions">
<button type="submit" class="btn btn-primary">Enregistrer</button>
Annuler
</div>
</form>
Symfony itself does not have any objects through which it is possible to manipulate the form's action as it is set in the html (twig files). However, twig provides the capability to dynamically change the form's action in the twig file.
The basic approach is for the controller to pass a parameter into the twig file via the render call. Then the twig file can use this parameter to set the form action dynamically. If the controller uses a session variable to determine the value of this parameter then by setting the value of this session variable in the test program it is possible to set the form action specifically for the test.
For example in the controller:
public function indexAction()
{
$session = $this->get('session');
$formAction = $session->get('formAction');
if (empty($formAction)) $formAction = '/my/normal/route';
...
return $this->render('MyBundle:XXX:index.html.twig', array(
'form' => $form->createView(), 'formAction' => $formAction)
);
}
And then, in the twig file:
<form id="myForm" name="myForm" action="{{ formAction }}" method="post">
...
</form>
And then, in the test program:
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->set('formAction', '/test/route');
$session->save();
// do the test
This isn't the only way, there are various possibilities. For example, the session variable could be $testMode and if this variable is set the form passes $testMode = true into the render call. Then the twig file could set the form action to one of two values depending on the value of the testMode variable.
Symfony2 makes a distinction between unit testing of individual classes and functional testing of application behaviour. Unit testing is carried out by directly instantiating a class and calling methods on it. Functional testing is carried out by simulating requests and testing responses. See symfony testing for further detail.
Form submission can only be tested functionally as it is handled by a Symfony controller which always operates in the context of a container. Symfony functional tests must extend the WebTestCase class. This class provides access to a client which is used to request URLs, click links, select buttons and submit forms. These actions return a crawler instance representing the HTML response which is used to verify that the response contains the expected content.
It is only appropriate to test that exceptions are thrown when carrying out unit tests as functional tests cover interaction with the user. The user should never be aware that an exception has been thrown. Therefore the worst case scenario is that the exception is caught by Symfony and in production the user is presented with the catch-all response "Oops, an error has occurred" (or similar customised message). However, this should really only occur when the application is broken and not because the user has used the application incorrectly. Therefore it is not something that would typically be tested for in a functional test.
Regarding the first scenario mentioned in the question - submitting a form with the wrong parameters. In this case the user should be presented with an error message(s) telling them what was wrong with their input. Ideally the controller should not be throwing an exception but symfony validation should be used to automatically generate error messages next to each field as appropriate. Regardless of how the errors are displayed this can be tested by checking that the response to submitting the form contains the expected error(s). For example:
class MyControllerTest extends WebTestCase
{
public function testCreateUserValidation()
{
$client = static::createClient();
$crawler = $client->request('GET', '/new-user');
$form = $crawler->selectButton('submit')->form();
$crawler = $client->submit($form, array('name' => '', 'email' => 'xxx'));
$this->assertTrue($crawler->filter('html:contains("Name must not be blank")')->count() > 0,
"Page contains 'Name must not be blank'");
$this->assertTrue($crawler->filter('html:contains("Invalid email address")')->count() > 0,
"Page contains 'Invalid email address'");
}
}
Regarding the second scenario - where the URL isn't complete. With Symfony any URL which does not match a defined route will result in a NotFoundHttpException. In development this will result in a message such as 'No route found for "GET /xxx"'. In production it will result in the catch-all 'Oops, an error has occurred'. It would be possible to test in development that the response contains 'No route found'. However, in practice it doesn't really make sense to test this as it's handled by the Symfony framework and is therefore a given.
EDIT:
Regarding the scenario where the URL contains invalid data which identifies an object. This could be tested (in development) like this in the unit test program:
$client = static::createClient();
$page = $client->request('GET', '/update/XXX');
$exceptionThrown = ($page->filter('html:contains("NotFoundException")')->count() > 0) && ($page->filter('html:contains("Unable to find ParkingZone entity")')->count() > 0);
$this->assertTrue($exceptionThrown, "Exception thrown 'Unable to find ParkingZone entity'");
If you just want to test that an exception has been thrown rather than a specific type / message you can just filter the html for 'Exception'. Remember that in production the user will only see "Oops, an error has occurred", the word 'Exception' will not be present.
Thanks to #redbirdo with his last answer, I found a solution without messing out with the controllers.
I only changed few lines in the templates.
ControllerTest
public function testUpdate()
{
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->set('testActionForm', 'abc');
$session->save(); // This line is important or you template won't see the variable
// ... tests
}
View
{% if app.session.has('testActionForm') %}
{% set actionForm = path('parkingzone_update', { 'id': app.session.get('testActionForm') }) %}
{% else %}
{% set actionForm = path('parkingzone_update', { 'id': entity.id }) %}
{% endif %}
<form action="{{ actionForm }}" {{ form_enctype(form) }} method="POST" class="form-horizontal">
// ... rest of the form

Symfony2 form collection: Index of the current object is shown

I've got a problem with displaying collection in my form.
When displaying my entity collection I've got something like this :
0
Name: myInputName
Address: myInputAddress
1
Name: myInputName
Address: myInputAddress
My question is why Symfony2 display the index...
And this for all saved entities into my collection...
Here the code I use:
$builder
->add('person', 'collection', array(
'label' => ' ',
'type' => new PersonType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
))
;
In my twig file:
<div>
{{ form_widget(edit_form) }}
</div>
Help please
Sam
Removing indexes (labels) for collection items:
$builder
->add('person', 'collection', array(
...
'options' => array('label' => false)
))
;
Use key entry_options instead of options for Symfony 3 and 4
If you want to add custom labels per row you can produce the form yourself:
{{ form_start(edit_form) }}
{% for person in form.persons %}
{{ form_row(person, {'label': 'custom label per item' }) }}
{% endfor %}
{{ form_end(edit_form) }}
Note: tested on Symfony 2.3 & 2.4
This one is some days ago but because I was facing same question for Symfony 3 the answer of sectus is the correct one.
Use the
'entry_options' => ['label'=>false],
option within your builder to hide he object item.
Best Regards
You can custom the rendering of your collection for don't display the index with, by example:
{% block _FORMNAME_person_widget %}
{% spaceless %}
{% for child in form %}
{{ form_widget(child.Name) }}
{{ form_widget(child.Address) }}
{% endfor %}
{% endspaceless %}
{% endblock %}
I know this has been closed for a while. And not sure if this has been solved elsewhere. This issue is actually pretty simple to fix and I am surprised there is no documentation about this anywhere. In the PersonType or any type that is used in a collections just modify the vars['name'] in the buildView to be what you want displayed as the label.
public function buildView(FormView $view, FormInterface $form, array $options)
{
// Adjust the view based on data passed
$this->vars['name'] = $form->getData();
// Or...
$this->vars['name'] = 'Some random string';
}
If you want it dynamic, you would use the object by form->getData(). Since, in my problem, I am using a form theme, overriding the twig is not really an option for me.
Hope this helps someone.
Using #MrBandersnatch's solution below, I had to use $view->vars['name'] instead of $this->vars['name'] (Symfony 2.3).
(apologies for not adding this as a comment on #MrBandersnatch's answer, I've not got enough reputation yet).

How to deal with Form Collection on Symfony2 Beta?

I have an entity User and an entity Address. There is a relation One-to-Many between User and Address :
class User
{
/**
* #orm:OneToMany(targetEntity="Address")
*/
protected $adresses;
[...]
}
I have a class AddressType, and class UserType :
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('addresses', 'collection', array('type' => new AddressType()));
}
[...]
}
In my controller, I build form with :
$form = $this->get('form.factory')->create(new UserType());
... and create view with :
return array('form' => $form->createView());
I display form field in my twig template with :
{{ form_errors(form.name) }}
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
[...]
Okay. Now, how to display fields for one or more addresses ? (it's no {{ for_widget(form.adresses.zipcode) }} nor {{ for_widget(form.adresses[0].zipcode) }} ...)
Any ideas ?
This is how I did it in my form template:
{{ form_errors(form.addresses) }}
{% for address in form.addresses %}
<div id="{{ 'address%sDivId'|format(loop.index) }}" class="userAddressItem">
<h5> Address #{{ loop.index }}</h5>
{{ form_errors(address) }}
{{ form_widget(address) }}
</div>
{% endfor %}
And I have a small action bar, driven by jQuery, that lets the user add and remove addresses. It is a simple script appending a new div to the container with the right HTML code. For the HTML, I just used the same output has Symfony but with updated index. For example, this would be the output for the street input text of the AddressType form:
<input id="user_addresses_0_street" name="user[addresses][0][street]" ...>
Then, the next index Symfony will accept is 1 so the new input field you add would look like this:
<input id="user_addresses_1_street" name="user[addresses][1][street]" ...>
Note: The three dots are a remplacement for required="required" maxlength="255" but could change depending on your needs.
You will need more HTML code than that to add a whole new AddressType to the DOM of the browser but this give you the general idea.
Regards,
Matt
I should top that up with the fact that if you want to dynamically add fields, you need to set the key 'allow_add' to true in your collection field in UserType :
...
$builder->add('addresses', 'collection', array(
'type' => new AddressType(),
'allow_add' => true
));
Just spent hours trying to figure out what was missing, and at the time i'm writing the doc does not mention this yet. Hope it'll help fellow developers.