Multiple entity in one form Symfony - php

I have an entity "ticket" and I would like to create a form with many of this entity.
So I created a FormType like this :
class TicketType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('quantity');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
public function getName()
{
return 'event';
}
}
And here my controller :
$clientTickets = array();
for ($i = 0; $i < 5; $i++) {
clientTickets[] = new Ticket();
}
$formTickets = $this->createForm(new TicketType(), array('tickets' => $clientTickets));
if ($request->getMethod() == 'POST') {
$formTickets->handleRequest($request);
for ($i=0; $i < $formTickets.size(); $i++) {
$em->persist($ticket[$i]);
}
}
And finally my twig :
{{ form_start(form, {'attr': {'class': 'form-horizontal', 'id': 'msform', 'name': 'myform', 'method': 'POST' }}) }}
{% for clientTicket in form.tickets %}
{{ form_widget(form.quantity) }}
{% endfor %}
{{ form_rest(form) }}
{{ form_end(form) }}
When I go on the page, I get this error :
Method "tickets" for object "Symfony\Component\Form\FormView" does not
exist in YourBundle:folder:view.html.twi at line
Why did I do wrong ? Have you got a solution ?

You may have to do something like this:
Let's have this form type:
class TicketType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder - > add('field1', 'text');
$builder - > add('field2', 'text');
$builder - > add('field3', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver - > setDefaults(array(
'data_class' = > null,
));
}
public function getName() {
return 'event';
}
}
Then in your controller you will need to have something like this:
public function testAction( )
{
$clientTickets = array(
'tickets' => array(
array(
'field1' => 'aaa',
'field2' => 'aaa',
'field3' => 'aaa'
),
array(
'field1' => 'bbb',
'field2' => 'bbb',
'field3' => 'bbb'
),
array(
'field1' => 'ccc',
'field2' => 'ccc',
'field3' => 'ccc'
)
)
);
$formTickets = $this->createFormBuilder( $clientTickets )
->add( 'tickets', 'collection', array( 'type' => new TicketType() ) )
->getForm();
if ($formTickets->handleRequest( $this->getRequest() )->isValid()) {
$data = $formTickets->getData();
var_dump( $data );
}
return array(
'form' => $formTickets->createView()
);
}
And your twig will look something like this:
{{ form_start(form) }}
<ul>
{% for clientTicket in form.tickets %}
<li>{{ form_row(clientTicket.field1) }} {{ form_row(clientTicket.field2) }} {{ form_row(clientTicket.field3) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
{{ form_end(form) }}
I think this code is self explanatory. Hope it helps

UPDATE:
I think you need to do something like this:
{% for i in 0..form|length %}
{{ form[i].method }}
{% endfor %}

Related

How to keep form data when browsing paginated output?

I have a symfony form that contains a long list of items with checkboxes. I have successfully made knp paginator work and now the form is split into pages. However, when I move between pages I lose the checked/unchecked status of the checkboxes. I am sure that this is probably quite easy but I cannot seem to work it out. I am not very experienced with php yet.
Here is my controller:
/**
* #Route("/addquestions/{quizid}", name="addquestions")
*/
public function add(Request $request, PaginatorInterface $paginator, $quizid)
{
$repository = $this->getDoctrine()->getRepository(Quiz::class);
$quiz = $repository->find($quizid);
$repository = $this->getDoctrine()->getRepository(Question::class);
$questions = $repository->findAll();
// Paginate the results of the query
$pagination = $paginator->paginate($questions, $request->query->getInt('page', 1), 3);
$form = $this->createForm(AddQuizQuestionType::class, $quiz, ["pagination" => $pagination]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$questions = $form->get('question')->getData();
foreach ($questions as $question){
$quizquestion = new QuizQuestion();
$quizquestion->setQuestion($question);
$quiz->addQuizQuestion($quizquestion);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($quiz);
$entityManager->flush();
return $this->redirectToRoute('homepage');
}
return $this->render('editquiz/addquestions.html.twig', [
'form' => $form->createView(),
'pagination' => $pagination,
]);
}
And my form - it uses the paginated query to display the items with a check box.
class AddQuizQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('question', EntityType::class, [
'expanded' => true,
'multiple' => true,
'class' => Question::class,
'mapped' => false,
'choices' => $options['pagination'],
])
->add('submit', SubmitType::class, [
'label' => 'Submit',
])
->setMethod('GET')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Quiz::class,
'pagination' => null,
]);
}
}
and my template - it renders the checkboxes with images from the database.
{% extends 'base.html.twig' %}
{% block title %}CreatequizController{% endblock %}
{% block body %}
<h1>Create Quiz</h1>
{{ form_start(form) }}
{% for question in form.question %}
<h5>Question {{ question.vars.value }}</h5>
<div class="border mb-5">
<img src="{{ asset(question.vars.label) }}" alt=""/>
<div class="pl-3">
{{ form_widget(question) }}
</div>
</div>
{% endfor %}
<div class="navigation">{{ knp_pagination_render(pagination) }}</div>
{{ form_end(form) }}
{% endblock %}
I would like it so that when I jump between pages the checkboxes that I have selected remain checked.
Thank you in advance,
Martyn

How to validate depending on the selected value?

I have a simple form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, ['required' => true])
->add('doYouWant', ChoiceType::class, ['choices' => ['no' => 'no', 'yes' => 'yes']])
->add('type')
;
}
I would like the user after the selection doYouWant to "yes" to have a mandatory "type" option, so I am trying:
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder) {
$data = $event->getForm()->getData();
if ($data['doYouWant'] == 'yes') {
$builder->add('type', null, ['required' => true]);
}
}
);
But it does not matter...
I think the simplest way would be to add constraints to each field from your type, and then, in template, by using jquery you can toggle the visibility of your type field based on the selected value from the dropdown.
# AppBundle/Form/ExampleType.php
$builder
->add('name', null, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('doYouWant', ChoiceType::class, [
'placeholder' => 'Select',
'choices' => ['no' => 'No', 'yes' => 'Yes'],
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('type', EmailType::class, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
new Email([
'message' => "The email '{{ value }}' is not a valid email",
])
]
])
;
I've added the type field as being of type email, just for testing purposes.
# Controller/DefaultController.php
/**
* #param Request $request
* #Route("/test", name="test")
* #return Response
*/
public function testAction(Request $request) : Response
{
$form = $this->createForm(ExampleType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($form->getData());die;
}
return $this->render('default/test.html.twig', [
'form' => $form->createView(),
]);
}
# default/test.html.twig (assuming you are using bootstrap and jquery)
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
{{ form_start(form, { attr: { 'novalidate': 'novalidate' } }) }}
<div class="form-group">
{{ form_label(form.name) }}
{{ form_widget(form.name,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.name) }}
</div>
<div class="form-group">
{{ form_label(form.doYouWant) }}
{{ form_widget(form.doYouWant,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.doYouWant) }}
</div>
<div class="form-group type hidden">
{{ form_label(form.type) }}
{{ form_widget(form.type,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.type) }}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Send</button>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
$(document).ready(function(){
$('#appbundle_example_doYouWant').change(function(){
var choice = $(this).val();
if (choice == 'Yes') {
$('.type').removeClass('hidden');
} else {
$('.type').addClass('hidden');
}
});
});
</script>
{% endblock %}
You can use validation groups and put your assertions inside your entity.
And then you can choose validation groups based on the submitted data like so:
https://symfony.com/doc/current/form/data_based_validation.html
How to add assertion on entity:
class MyEntity {
/**
* #Assert\NotBlank(groups={"YourGroup"})
*/
protected $type;
}
Then on your form:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
$want = $data->getDoYouWant();
if ($want) {
return ['YourGroup'];
}
return ['Default'];
},
));
}

Symfony 4 Form Collection with prototypes

I'm new to Symfony 4 and I'm trying to render a form with a ChoiceType Field with numeric choices in order to generate the exact Number of tags Chosen by the user.
This is my controller:
class ContactController extends AbstractController
{
/**
* #Route("/matrix", name="matrix")
*/
public function index(Request $request)
{
$contact = new Contact();
// i've already added some tags
$tag3 = new Tag();
$tag3->setName('tag3');
$contact->getTags()->add($tag3);
$tag4=new Tag();
$tag4->setName('ciao');
$contact->getTags()->add($tag4);
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contactFormData = $form->getData();
dump($contactFormData);
}
return $this->render('contact/index.html.twig', array(
//'our_form' => $form,
'form' => $form->createView(),
));
}
At this Point of my code, the form seems to be filled, I've checked with some dumps.
This is my twig
{% block body %}
<div>
{{ form_start(form) }}
{{ form_widget(form) }}
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{% for tag in form.tags %}
<li> {{ form_row(tag.name) }}
</li>
{% endfor %}
</ul>
<input type="submit" value="Send" class="btn btn-success" />
{{ form_end(form) }}
</div>
{% endblock %}
It seems there's no visibility between this two Files, in fact, he is not able to enter into the for a loop. I've dumped some stuff and I've seen that tags have no children at this Point but it should.
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('motto')
->add('expectations', ChoiceType::class, array(
'choices' => array(
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
),
));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'by_reference' => false,
'mapped' => false,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
I'm perplex about this code => $contact->getTags()->add($tag3);. It seems Tags to be an entity and Contact another one so your Contact entity should have adders/removers and/or setters/removers (event if cumulate both is not necessary).
So your entity should like:
class Contact
{
// ...
/** #var Collection */
protected $tag;
// ...
public function __construct()
{
$this->tags = new ArrayCollection();
}
// ...
public function addTag(Tag $tag)
{
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
// ...
}
}
A good example to implement your case: How to Embed a Collection of Forms
Then i don't know what's your TagType form looks like but even if it's well develop your twig is not ok.
First form_widget(form) render the entire form
From Symfony Doc
Renders the HTML widget of a given field. If you apply this to an entire form or collection of fields, each underlying form row will be rendered.
So re-render the collection will have no effect. And even if your twig code is not te good one to render a collection.

Craue form flow returns false on $flow->isValid() in last step

I made form wizard with CraueFormFlowBundle and it works until last step where $flow->isValid() fails and it constantly renders last step instead of going to desired end of form wizard action.
Here is the code necessary to understand where I made mistake:
Controller:
public function indexAction(Activity $activity)
{
$currency = $this->getDoctrine()->getRepository('MycompanyUtilBundle:UtilCurrency')->find(1);
$booking = new Booking();
$booking->setActivity($activity)
->setCurrency($currency)
->setReferenceCode(uniqid())
->setUserAccount($this->getDoctrine()->getRepository('MycompanySettingsBundle:UserAccount')->find(1));
$flow = $this->get('mycompany.form.flow.Booking');
$flow->bind($booking);
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
$form = $flow->createForm();
} else {
return new JsonResponse(['Status' => 'Form is valid']);
}
}
return $this->render(
'#MycompanyDemoBundle/Default/booking.flow.html.twig',
[
'form' => $form->createView(),
'flow' => $flow,
'activity' => $activity->getTitle(),
'formData' => $booking,
'error' => $form->getErrors(true, true),
]
);
}
services.yml:
mycompany.form.Booking:
class: mycompany\BookingBundle\Form\BookingType
tags:
- { name: form.type, alias: mycompany_bookingbundle_booking_form }
mycompany.form.flow.Booking:
class: mycompany\BookingBundle\Form\BookingFlow
parent: craue.form.flow
scope: request
calls:
- [ setFormType, [ #mycompany.form.Booking ] ]
BookingType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
switch ($options['flow_step']) {
case 1:
$builder
->add(
'activity',
'entity',
[
'class' => 'Mycompany\ActivityBundle\Entity\Activity',
'property' => 'title',
]
)
->add(
'scheduledDeparture',
'entity',
[
'class' => 'Mycompany\ActivityBundle\Entity\ActivityScheduledDeparture',
'property' => 'departureDateTimeString',
'empty_value' => 'Select departure time',
]
)
->add(
'payer',
new CrmContactType()
);
break;
case 2:
$builder
->add(
'numberOfAdults',
'choice',
[
'choices' => range(1, 5),
'empty_value' => 'Select number of adult travellers'
]
)
->add(
'numberOfChildren',
'choice',
[
'choices' => range(1, 5),
'empty_value' => 'Select number of child travellers'
]
);
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'Mycompany\BookingBundle\Entity\Booking',
]
);
}
BookingFlow:
protected function loadStepsConfig()
{
return
[
[
'label' => 'Select tour',
'type' => $this->formType,
],
[
'label' => 'Select travellers',
'type' => $this->formType,
],
[
'label' => 'Confirmation',
],
];
}
and at the end twig file:
{% stylesheets '#CraueFormFlowBundle/Resources/assets/css/buttons.css' %}
<link type="text/css" rel="stylesheet" href="{{ asset_url }}"/>
{% endstylesheets %}
<div>
Steps:
{% include '#CraueFormFlow/FormFlow/stepList.html.twig' %}
</div>
{{ form_start(form) }}
{{ form_errors(form) }}
{{ activity }}
{{ form_rest(form) }}
{% include '#CraueFormFlow/FormFlow/buttons.html.twig' %}
{{ form_end(form) }}
<div>
Errors:
{{ dump(error) }}
</div>
<div>
Form:
{{ dump(form) }}
</div>
There is no extra validation at all, only that field cannot be empty. I get to the last step, and when I click Finish button I thought Symfony will generate JSON with "Form is valid" value, but all I got is last step over and over again.
When I debugged that section, clicking on finish gives false to $flow->isValid($form) although every previous step was true and I cannot get to returning JSON response here.
I'm also dumping form and form->getErrors values in twig but nothing resembles that I'm having some error. If I try to persist that data it is successfully persisted.
I didn't find solution for this on git page of bundle.
Do you guys here maybe know where should I look for the solution?
Lines for the solution was this:
app/config/config.yml
csrf_protection:
enabled: true
Now when I figured that, it makes sense why it needs csrf_token to work with current workflow given on CraueFormFlowBundle example

Show input in embedded form in symfony2

I'm trying to create an embedded form in symfony2 that will consist of a Contact form that will have embedded a ContactPhonenumber form to add phone numbers.
I have followed the instructions in Symfony2 documentation about embedding forms but I can't find a way to show at least an input field for phonenumbers. If I do it without errors, I only get back a label for the embedded phonenumber form.
How can I show an input there?
My code is:
My two entity form classes:
class ContactType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('contactType')
->add('title')
->add('name')
->add('givenName')
->add('middleName')
/*->add(
'contactPhonenumbers',
'collection',
array( 'type' => new ContactPhonenumberType($options) )
)
*/
->add('contactPhonenumbers', new ContactPhonenumberType($options) )
;
}
//.......
//More functions
//.......
}
class ContactPhonenumberType extends AbstractType
{
public function __construct(array $options=null) {
$this->options = $options;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('position')
->add('number')
->add(
$builder->create(
'contact',
'searchable',
array(
'data_class' => '....Bundle\Entity\Contact',
'class' => '....Bundle:Contact',
'dataroute' => '...._contact_searchable',
'classlistview' => new ....Bundle\Component\Listview\ContactList
)
)->addViewTransformer(
new NameToIdTransformer(
$options[ 'em' ],
$options[ 'request' ],
$this,
array(),
'....Bundle:Contact'
)
)
)
->add(
$builder->create(
'phonenumberType',
'searchable',
array(
'data_class' => '....Bundle\Entity\PhonenumberType',
'class' => '.....Bundle:PhonenumberType',
'dataroute' => '...._phonenumbertype_searchable',
'classlistview' => new ....\Component\Listview\PhonenumberTypeList
)
)->addViewTransformer(
new NameToIdTransformer(
$options[ 'em' ],
$options[ 'request' ],
$this,
array(),
'...Bundle:PhonenumberType'
)
)
)
;
}
}
And my function on controller
public function newAction( Request $request )
{
$em = $this->getDoctrine()->getManager();
$entity = new Contact();
// $number = new ArrayCollection();
// $number[] = new ContactPhonenumber();
// $entity->setContactPhonenumbers( $number );
$form = $this->createCreateForm( $entity );
return $this->render(
'...Bundle:Contact:new.html.twig',
array(
'entity' => $entity,
'form' => $form->createView()
)
);
}
And the form in twig:
<div class="widget-body">
{{ form_start( form, { 'attr': { 'class': 'form-horizontal', 'role': 'form', 'novalidate': 'novalidate' } } ) }}
{{ form_errors( form ) }}
{{ form_row( form.contactType ) }}
{{ form_row( form.title ) }}
{{ form_row( form.name ) }}
{{ form_row( form.givenName ) }}
{{ form_row( form.middleName ) }}
------>
{{ form_row(form.contactPhonenumbers) }}
<--------
{{ form_row( form.salutation ) }}
{{ form_row( form.address ) }}
{{ form_row( form.superiorContact ) }}
<div class="form-group">
<hr>
</div>
<div class="form-group">
<div class="col-sm-2"></div>
<div class="col-sm-10">
{{ form_widget( form.submit ) }}
{{ form_widget( form.cancel ) }}
</div>
</div>
{{ form_row( form._token ) }}
{{ form_end( form, { 'render_rest': false } ) }}

Categories