How to get the changed entities only, after a submit? - symfony

I have a collection of forms like in the code below, which leads to a form that looks like the following (with two example entries):
When clicking submit, the data i provided to the form (builder) instance is updated accordingly.
Problem: The problem i have is, that the list can be quite long, so i need a way of knowing which instances have been updated.
I thought about storing a clone of the original data (here $leadPartnerList in my session. But that does not feel right.
Does symfony (specifically form builder) provide such functionality out of the box? Or what would be an efficient solution to know which fields in the form have been updated and which not?
My Twig:
{% block content %}
<div>
{{ form_start(form) }}
{% for partner in lead_partners %}
{{ form_row(partner.name) }}
{% endfor %}
{{ form_end(form) }}
</div>
{% endblock content %}
My Controller Code:
public function overview(Request $request, \App\Utility\LeadPartnerLoader $LeadPartnerLoader)
{
$leadPartnerList = $leadPartnerLoader->loadAll();
$formBuilderData = [
'lead_partners' => $leadPartnerList
];
$listForm = $formFactory->createNamedBuilder('listForm', FormType::class, $formBuilderData)
->add('lead_partners', CollectionType::class, [
'entry_type' => LeadPartnerFormType::class,
'allow_add' => true
])
->add('submit', SubmitType::class, [
'label' => 'Submit Changes'
])
->getForm();
... handleRequest and so on and so forth...
}
And the Form Type (LeadPartnerFormType):
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => LeadPartner::class,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class);
}
$leadPartnerList is of type array with LeadPartner instances within each entry of the array.
PLEASE NOTE: I'm not using Doctrine here!

Use symfony EventListener or EventSubscriber. See: https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html

Related

How include an attribute 'class' rendering a form

I have a contact form and I'm trying to put a class to an input of my form.
This is in the ContactType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Contact::class,
]);
}
And this in my twig:
{{ form_start(form) }}
{{ form_widget(form.name, {attr: {class: 'myclass', placeholder: 'name'} }) }}
{{ form_end(form) }}
I get this html:
<input type="text" class="form-control" id="contact_name" name="contact[name]" required="required" placeholder="name">
It's working for placeholder, but not to class. I have tried to put the attr in the builder and it is the same. Is the class form-control overwriting my class? How can I fix it?
I think you're using a bootstrap form theme, check into the config file.
and if the case, you can add this line on the top of your twig file :
{% form_theme form 'form_div_layout.html.twig' %}
Try this if it will help you
{{ form_start(form) }}
{{ form_widget(form.name, {'attr': {'class': 'input-text input-text--white', 'placeholder': 'name'} }) }}
{{ form_end(form) }}
I believe you can't define two attributes on form_wdiget. The way around this is doing both in the FormBuilder or one in the FormBuilder.
You can define attributes on a form element like so:
$builder
->add('name', TextType::class, [
'attr' => [
'class' => 'my-class',
],
]);
Note that for a CSS class, it must be in the array attr, not to be confused with a class as in your entity like on the EntityType::class.
Or
$builder
->add('name', TextType::class, [
'placeholder' => 'My place holder',
]);
So you could achieve both like:
$builder
->add('name', TextType::class, [
'attr' => [
'class' => 'my-class',
],
'placeholder' => 'My place holder',
]);
You can read more about form field attributes here.
You can read it from the official page:
https://symfony.com/doc/current/reference/forms/types/form.html#attr
Just add: {'attr': {'attribute name': 'value'}}
As shown below
{{ form_widget(form.propertyName, {'attr': {'placeholder': 'name'} }) }}

Getting the string of the chosen option in a drop down that comes from an entity repository

I am trying to pull in a repository into a form in Symfony 3.4 and then use the chosen option when the form is submitted.
Here's the form code:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('text', TextareaType::class, [
'label' => 'Text'
])
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'query_builder' => function(CategoryRepository $repo) {
return $repo->createQueryBuilder('c')
->groupBy('c.name');
}
])
->add('subcategory', EntityType::class, [
'class' => Category::class,
'choice_label' => 'subcategory',
'query_builder' => function(CategoryRepository $repo) {
return $repo->createQueryBuilder('c')
->groupBy('c.subcategory');
}
]);
}
With this I can render the form and it looks good. I can choose the various options in the CategoryRepository.
{% block body %}
{{ form_start(form) }}
{{ form_label(form.name) }}
{{ form_errors(form.name) }}
{{ form_widget(form.name) }}
{{ form_label(form.subcategory) }}
{{ form_errors(form.subcategory) }}
{{ form_widget(form.subcategory) }}
{{ form_end(form) }}
On submit, when checking with Xdebug, the category is the object Category. I can see the correct values present (those chosen in the drop down of the form) but I want just the string, e.g. category.name. How do I do that?
Also, it might need a different question, but when I select one of the categories, I'd like the subcategory to be updated to exclude those that don't belong to that chosen category. I realise this may require jquery.
I'm certain there are better ways, but one solution would be to
$category = $data->getCategory();
$data->setCategory($category->getName());
$data->setSubcategory($category->getSubcategory());
And to exclude the subcategories I use jQuery / JavaScript to retrieve the filtered results from a controller / repository, then remove or add those options in the HTML.

The form's view data is expected to be an instance of MyEntity but is an instance of ArrayCollection

I can create a form which works fine but getting errors when trying to flush data to the database:
The controller action looks like this:
$purchase = new Purchase();
$form = $this->createFormBuilder($purchase)
->add('addresses', AddressType::class)
->add('quantity', IntegerType::class)
->add('toBeDeliveredAt', DateType::class)
->add('comment', TextareaType::class)
->add('save', SubmitType::class, array('label' => 'Submit'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($purchase);
$em->flush();
}
Class AddressType looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('firstname', TextType::class)
->add('lastname', TextType::class)
->add('street', TextType::class)
->add('city', TextType::class)
->add('zipcode', TextType::class)
->add('country', TextType::class)
->add('phone', TextType::class)
->add('email', EmailType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'XxxBundle\Entity\Address'
));
}
But that gives me the following error:
The form's view data is expected to be an instance of class
XxxBundle\Entity\Address, but is an instance of class
Doctrine\Common\Collections\ArrayCollection. You can avoid this error
by setting the "data_class" option to null or by adding a view
transformer that transforms an instance of class
Doctrine\Common\Collections\ArrayCollection to an instance of
XxxBundle\Entity\Address.
If I set the data_class option to null I get
Warning: spl_object_hash() expects parameter 1 to be object, string given
How can I add a view transformer that transforms an instance of class ArrayCollection to an instance of Address?
Addresses is an array collection so you have to use it as an collection in your form like this :
$builder->add('addresses', CollectionType::class, array(
'entry_type' => AddressType::class
));
Instead of this :
$builder->add('addresses', AddressType::class)
In this way you embed Address Form into Purchase Form
You can see the doc about embed a collection of form here
Explanations (sorry for my bad English) :
You have to think about object.
In your Purchase Entity you have a collection of Address because a Purchase object can have many address that is to say to persist Purchase object you have to give a collection of addresses.
So when you built your Purshase form, Symfony expects an array collection for addresses input. It is what I wrote above. With this, if you are in the case where you want to add a new Purchase object, when you go to view the form, to display fields corresponding to the Address, you have to do this into your form:
<ul class="addresses">
{# iterate over each existing address and render these fields #}
{% for address in form.addresses %}
<li>{{ form_row(address.title) }}</li>
//do this for all the others fields ...
{% endfor %}
</ul>
But if you want to add many address dynamically, you have to do 2 things :
First, you have to do add 'allow_add' => true in the form builder
$builder->add('addresses', CollectionType::class, array(
'entry_type' => AddressType::class,
'allow_add' => true,
));
Second, the allow_add also makes a "prototype" variable available to you in the form html generated : this is where Javascript come in :)
To do work this, you have to write some Javascript and you can see a good example here
I hope that it's clearer

Symfony Forms - How to Change a CollectionTypes Items labels

This is my first question on stackoverflow; until now I just looked for answers for my issues and found them. But now it seems that I have one that nobody or at least no one here stumbled across.
About my problem:
In my Class FieldType extends AbstractType I want to change the labels of CollectionType items.
This works for all contained items, but I want to set the labels individually:
$translations = $builder->create("translations", CollectionType::class, array(
"label" => "Translations",
"type" => TranslationType::class,
"entry_options" => array(
'label' => "THIS IS A TEST"
)
));
Here I add new types to the CollectionType and try to set each item's label:
foreach ($this->field->getTranslations() as $translation) {
/** #var Language $language */
$iso2 = strtolower($translation->getLanguage()->getIso2());
$translation = $builder->create("translation_{$iso2}", TranslationType::class, array(
'label' => $iso2,
)
);
$translations->add($translation);
}
$builder->add($translations);
But they are not displayed in the template; I suppose that here the indices of the Collection are shown (0,1,...). (see Rendered Translations FormView)
This is my TranslationType:
class TranslationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add("label");
$builder->add("placeholder");
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Translation::class,
));
}
}
I got here by searching for exactly the same question.
The 0,1,... probably are the keys of ArrayCollection of your entity. But that doesn't help.
The methods below are not pretty, but they should work. I'll be watching this thread for better suggestions...
Method 1
In your FormType class, add:
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
...
/**
* this gets called in the final stage before rendering the form
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
foreach ($view['translations']->children as $childView)
{
// here you have access to all child form vars, assigned entities, etc
$childView->vars['label'] = ''; // set label here
}
}
Method 2:
Just customize the template, as per http://symfony.com/doc/current/cookbook/form/form_customization.html
Instead of {{ form_label() }}, use something else :)
As I said, it is not pretty, but hope it helps.
Thanks for the answer #Karolis. Just in case this might be interesting for someone else. I was also facing this issue trying to customize the label of individual collection items, and I was trying for a while to do it through twig like so:
{% for key, child in successStoryForm.content.children %}
<div id="successStory_content">
<div class="form-group">
<div id="{{ child.vars.id }}">
<div class="form-group">
<label class="control-label required" for="{{ child.vars.id }}_text">{{ contentHeaders[key] }}</label>
<textarea id="{{ child.vars.id }}_text" name="{{ child.vars.full_name }}[text]" required="required" class="form-control" placeholder="<p>Dein Text</p>"><p>Dein Text_2</textarea>
<span class="help-block">
<ul class="list-unstyled">
<li>
<span class="glyphicon glyphicon-exclamation-sign"></span>
{{ dump(child.vars.errors) }}
</li>
</ul>
</span>
</div>
</div>
</div>
</div>
{% endfor %}
This actually rendered the label correctly, however, I was not able to render the errors properly. They were always empty.
In my main formtype I add this collection using:
->add('content', CollectionType::class, [
'entry_type' => SuccessStoryContentType::class,
'required' => true,
'entry_options' => [
'label' => false,
],
])
The SuccessStoryContentType is a simple form type with some validation (left out here)
$builder
->add('text', TextareaType::class);
To get this to work I had to dig a little deeper into the form views children like so:
foreach ($view['content']->children as $key => $childView)
{
$childView->vars['form']->children['text']->vars['label'] = $label;
}

How to pass another entity info into form in Symfony2?

What is best practice to include another info in form from another entity?
I have Student Entity, Group Entity, StudentsGroup and Attendance Entity.
Student have id, code, and name.
Group have id and name.
StudentsGroup have id, group_id, and student_id.
Attendance have id, students_group_id, date and status.
Group can have many Student which saved in StudentsGroup. Why I make StudentsGroup? Because actually 1 Group can have some sub groups like SubjectsGroup etc. And Attendance save student information by StudentsGroup Id which same student can have different students_group_id.
Now, the problem is : How to show Student information in collection form of attendance?
All Entity relationship is declared as object, so actually we can access em freely from any entity. But I don't know how to do that in form. Here my form :
<?php
/* Collection Form */
$tanggal = new \DateTime($request->request->get('sifo_adminbundle_studentsgrouping')['tanggal']);
$attendances = new StudentsGrouping();
foreach ($entities as $temp) {
$entity = new Attendance();
$entity = $em->getRepository('SifoAdminBundle:Attendance')->findOneBy(array('studentsGrouping' => $temp, 'date' => $tanggal));
if ($entity){
$attendances->getAttendances()->add($entity);
}
}
$form = $this->createCollectionForm($attendances, $id, $tanggal);
return $this->render('SifoAdminBundle:DftAbsensi:manage.html.twig', array(
'form' => $form->createView(),
));
This is how I render it in twig :
{{ form_start(form_collection) }}
{{ form_row(form_collection.tanggal) }}
{% for attendance in form_collection.attendances %}
{{ form_row(attendance.status) }}
{% endfor %}
{{ form_end(form_collection) }}
## Concept ##
I'm thinking about creating entity and pass it into form like this :
foreach ($entities as $temp) {
$entity = new Student();
$entity = $em->getRepository('SifoAdminBundle:Student')->find($temp->getId());
if ($entity){
$entities[i] = $entity;
}
$i++
}
and then in twig show it like this :
{{ form_start(form_collection) }}
{{ form_row(form_collection.tanggal) }}
{% for key, attendance in form_collection.attendances %}
{{ entities[key].code }}
{{ entities[key].name }}
{{ form_row(attendance.status) }}
{% endfor %}
{{ form_end(form_collection) }}
But I feel not comfort with this. Am I really need to make new entity just for showing name and code from Student Entity? Is there a best practice to do this?
You can use entity field.
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'multiple' => true, /* you can choose more than one */
'mapped' => false, /* if you are using the form with an entity */
'query_builder' => function(EntityRepository $er) {
/* use query builder to get correct results */
return $er->createQueryBuilder('u')->orderBy('u.username', 'ASC');
},
));
There are two important keys in above.
'multiple' => true ----- you can able to choose more than one
'mapped' => false ----- if you are using form with an entity, your form will automatically looks for a connection between these entities and if can not found, throws exception. to avoid of that problem you should set this option to false
http://symfony.com/doc/current/reference/forms/types/entity.html

Resources