so I have this Symfony Form which I created, and there are these 3 inputs that are related to each other. I'm trying to align all three of them in the same line to make it looks better.
Here's my code:
the FormType
<?php
namespace App\Form;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use App\Entity\TypeParking;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TypeParkingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('libelle')
->add('tempsmax')
->add('jourdebut')
->add('jourfin')
//these three are the fields that I'm trying to put into a single line
->add('jourstravail_jour', TextType::class, ['property_path' => 'jourstravail[jour]'])
->add('jourstravail_debut', TextType::class, ['property_path' => 'jourstravail[debut]'])
->add('jourstravail_fin', TextType::class, ['property_path' => 'jourstravail[fin]'])
->add('Exception_Name', TextType::class, ['property_path' => 'exception[name]'])
->add('Starting_date', TextType::class, ['property_path' => 'exception[datedebut]'])
->add('Ending_date', TextType::class, ['property_path' => 'exception[datefin]'])
->add('Starting_time', TextType::class, ['property_path' => 'exception[heuredebut]'])
->add('Ending_time', TextType::class, ['property_path' => 'exception[heurefin]'])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => TypeParking::class,
]);
}
}
and here's my _form.html.twig file
{{ form_start(form) }}
{{ form_row(form.libelle, { 'label': 'Label' }) }}
{{ form_row(form.tempsmax, { 'label': 'Max Time' }) }}
{{ form_row(form.jourdebut, { 'label': 'Start Date' }) }}
{{ form_row(form.jourfin, { 'label': 'Ending Date' }) }}
<h3>these are the fields</h3>
{{ form_row(form.jourstravail_jour, { 'label': 'Day' }) }}
{{ form_row(form.jourstravail_debut, { 'label': 'start' }) }}
{{ form_row(form.jourstravail_fin, { 'label': 'end' }) }}
<h3>Exception</h3>
{{ form_row(form.Exception_Name, { 'label': 'Exception Name' }) }}
{{ form_row(form.Starting_date, { 'label': 'Starting Date' }) }}
{{ form_row(form.Ending_date, { 'label': 'Ending Date' }) }}
{{ form_row(form.Starting_time, { 'label': 'Starting Time' }) }}
{{ form_row(form.Ending_time, { 'label': 'Ending Time' }) }}
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
The fields that I'm trying to format are jourstravail_jour, jourstravail_debut and jourstravail_fin
Here is an answer based on my comments that seemed to help you, so you can tag your questions as answered.
You can use it "normally", by using form_label() and form_widget()(not sure about this one) and wrap them with Bootstrap classes.
Or you can customize the form_row() behaviour and include your classes in your own form templates.
Related
Currently I am working on a recipe app in Symfony 6. So far so good. Right now I want to enable the user to change his username. The username is at the same time also the UserIdentifier.
The username can also be changed successfully. I have a constraint that sets the length of the username. However, the form with the invalid value is submitted anyway and the user is logged out.
Maybe I made a mistake somewhere, but I'm really getting desperate. Actually, the problem is quite simple, but I can't find the right solution.
Thank you very much for your help!
My Controller
`
<?php
namespace App\Controller;
use App\Form\ProfileUsernameType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class ProfileChangeUsernameController extends AbstractController
{
#[Route('/profile/changeUsername')]
public function changeUsername(UserRepository $userRepository, EntityManagerInterface $entityManager, Request $request)
{
$user = $this->getUser()->getUserIdentifier();
$username = $userRepository->findOneBy(['username' => $user ]);
$form = $this->createForm(ProfileUsernameType::class, $username);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$username->setUsername($form->get('username')->getData());
$entityManager->persist($username);
$entityManager->flush();
}
return $this->render('profile/changeUsername.html.twig', [
'form' => $form,
]);
}
}
`
My Form
`
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class ProfileUsernameType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username', TextType::class,[
'data_class' => null,
'attr' => [
'placeholder' => 'Username',
'autocomplete' => 'off',
],
'constraints' => [
new NotBlank(),
new Length(['min' => 5])],
])
->add('submit', SubmitType::class, ['label' => 'Submit your username'])
->getForm();
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
`
My Twig file
`
{% extends 'base.html.twig' %}
{% form_theme form 'bootstrap_5_layout.html.twig' %}
{% block body %}
{{ form_start(form)}}
{{ form_errors(form) }}
{{ form_widget(form.username) }}
{{ form_row(form.submit) }}
{{ form_end(form) }}
{% endblock %}
`
I was facing the same problem some time ago and found this solution:
In your ProfileUsernameType.php, do it like this:
$builder
->add('username', TextType::class, [
'attr' => [
'class' => 'form-control',
'readonly' => 'readonly',
'value' =>$user['username']
],
'label' => 'Username: '
])
I made a form with Symfony. I am using CollectionType class in order to send several information from one input that I can duplicate. (see my code below)
The field is missing. I didn't succeed to get a select and choose the language I wanted. Instead I have all code in data-prototype which is unexpected. (see the picture at the bottom)
So here is what I did :
registration.html.twig
{% extends 'base.html.twig' %}
{% block main %}
{{ form_start(userform) }}
{{ form_row(userform.wishedlanguages) }}
<button type="button" id="addwishedlanguage">+ Add language</button>
<button type="button"id="removewishedlanguage">- Delete language </button>
{{ form_row(userform.save }}
{{ form_end(userform) }}
{% endblock %}
UserType.php
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('wishedlanguages', CollectionType::class, [
'entry_type' => LanguageType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'label' => 'Quelle langue souhaites-tu pratiquer ?',
])
->add('save', SubmitType::class, [
'label' => 'Je valide',
])
;
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'translation_domain' => 'forms'
]);
}
}
Here is the code I can see in the browser :
I need to create a form adding faculty to the database. First, the user selects a region from the list ( / ChoiceType), then the city of this region from the following list, the university, and finally enters the name of the faculty. The default values are the first region from the database, its first city and first university.
Sending the page with default data works, the choice of the region works, but the the choice of the city return to 500 status
Form:
Twig and ajax:
{% extends 'admin/insert/insert.html.twig' %}
{% block title %}Add Faculty{% endblock %}
{% block body %}
<div class="insert">
<h1 class="insert__title">Add Faculty</h1>
{{ form_start(insert_faculty, { 'attr' : {'class' : 'insert__form'} }) }}
{% for message in app.flashes('success') %}
<div class="insert__success">
{{ message }}
</div>
{% endfor %}
<div class="insert__errors">
{{ form_errors(insert_faculty) }}
</div>
{{ form_label(insert_faculty.region, 'Region:', { 'label_attr' : {'class' : 'insert__label'} }) }}
{{ form_widget(insert_faculty.region, { 'attr' : {'class' : 'insert__input'} }) }}
{{ form_label(insert_faculty.city, 'City:', { 'label_attr' : {'class' : 'insert__label'} }) }}
{{ form_widget(insert_faculty.city, { 'attr' : {'class' : 'insert__input'} }) }}
{{ form_label(insert_faculty.university, 'University:', { 'label_attr' : {'class' : 'insert__label'} }) }}
{{ form_widget(insert_faculty.university, { 'attr' : {'class' : 'insert__input'} }) }}
{{ form_label(insert_faculty.name, 'Name:', { 'label_attr' : {'class' : 'insert__label'} }) }}
{{ form_widget(insert_faculty.name, { 'attr' : {'class' : 'insert__input insert__input_name'} }) }}
<button type="submit" class="insert__button">Save</button>
{{ form_end(insert_faculty) }}
<div class="insert__buttons">
Back
</div>
</div>
{% block javascripts_footer %}
{{ parent() }}
<script>
let $region = $('#insert_faculty_region');
$region.change(function() {
let $form = $(this).closest('form');
let data = {};
data[$region.attr('name')] = $region.val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(get) {
$('#insert_faculty_city').html(
$(get).find('#insert_faculty_city').html()
);
$('#insert_faculty_university').html(
$(get).find('#insert_faculty_university').html()
);
}
});
});
let $city = $('#insert_faculty_city');
$city.change(function() {
let $form = $(this).closest('form');
let data = {};
data[$city.attr('name')] = $city.val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(get) {
$('#insert_faculty_university').html(
$(get).find('#insert_faculty_university').html()
);
}
});
});
</script>
{% endblock %}
{% endblock %}
Form class:
class InsertFacultyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('region', ChoiceType::class, [
'choices' => $options['regions_array'],
'mapped' => false,
])
->add('city', ChoiceType::class, [
'choices' => null,
'mapped' => false,
])
->add('university', ChoiceType::class, [
'choices' => null,
])
->add('name')
;
$formModifier = function (FormInterface $form, $entity_parent) {
if (get_class($entity_parent) === 'App\Entity\Region') {
if (!$entity_parent->getCities()->count()) {
$form->add('city', ChoiceType::class, [
'choices' => null,
'mapped' => false,
]);
}
else {
$cities_in_database = $entity_parent->getCities();
foreach ($cities_in_database as $city) {
$cities[$city->getName()] = $city;
}
$form->add('city', ChoiceType::class, [
'choices' => $cities,
'mapped' => false,
]);
}
}
else if (get_class($entity_parent) === 'App\Entity\City') {
if (!$entity_parent->getUniversities()->count()) {
$form->add('university', ChoiceType::class, [
'choices' => null,
]);
}
else {
$university_in_database = $entity_parent->getUniversities();
foreach ($university_in_database as $university) {
$universities[$university->getName()] = $university;
}
$form->add('university', ChoiceType::class, [
'choices' => $universities,
]);
}
}
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($options, $formModifier, $builder) {
$region = $options['regions_array'][array_key_first($options['regions_array'])];
$city = $region->getCities()[0];
$formModifier($event->getForm(), $region);
$formModifier($event->getForm(), $city);
}
);
$builder->get('region')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$region = $event->getForm()->getData();
$city = $region->getCities()[0];
$formModifier($event->getForm()->getParent(), $region);
$formModifier($event->getForm()->getParent(), $city);
}
);
$builder->get('city')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$city = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $city);
}
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Faculty::class,
'regions_array' => null,
]);
$resolver->setAllowedTypes('regions_array', 'array');
}
}
Controller:
/**
* #Route("/admin/insert/faculty", name="faculty")
*/
public function faculty(Request $request)
{
$regions_in_database = $this->getDoctrine()->getRepository(Region::class)->findAll();
$regions = [];
foreach ($regions_in_database as $region) {
$regions[(string)$region->getName()] = $region;
}
$faculty = new Faculty();
$insert_faculty = $this->createForm(InsertFacultyType::class, $faculty, [
'regions_array' => $regions,
]);
if (!$regions_in_database) {
$insert_faculty->addError(new FormError("There are no regions!"));
}
$insert_faculty->handleRequest($request);
if ($insert_faculty->isSubmitted() && $insert_faculty->isValid()) {
$repository = $this->getDoctrine()->getRepository(University::class);
$faculty_in_database = $repository->findOneBy(
[
'name' => $faculty->getName(),
'university' => $faculty->getUniversity(),
]
);
if ($faculty_in_database) {
$insert_faculty->addError(new FormError('Such a faculty is already in the database!'));
}
else {
$faculty->setRating(0);
if(!$faculty->getUniversity()) {
$insert_faculty->addError(new FormError("Select the university!"));
}
else {
$entity_manager = $this->getDoctrine()->getManager();
$entity_manager->persist($faculty);
$entity_manager->flush();
$this->addFlash(
'success',
'Faculty "' . $faculty->getName() . '" successfully saved!'
);
}
}
}
return $this->render('admin/insert/faculty/faculty.html.twig', [
'insert_faculty' => $insert_faculty->createView(),
]);
}
I invite you to use the Symfony debug toolbar. It will allow you to see the different problems associated with your code and give much more information about the problems that appear.
Profiler Symfony
About your problem, I think it is necessary to debug at the level of what your form sends to your application. But if you want more help, you must provide the error message that come with your 500 error.
I've worked through a few of the Forms-Tutorials on the Symfony-Page (especially How to Embed a Collection of Forms, How To use a Form without a Dataclass & CollectionType Field ).
I'm trying to show a form with multiple lead partners which can be edited and submitted back to the system.
But i get a Twig_Runtime_Error saying: ''Variable "lead_partners" does not exist''.
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();
$form = $this->createFormBuilder($leadPartnerList)
->add('lead_partners', CollectionType::class, [
'entry_type' => LeadPartnerFormType::class,
])->getForm();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$data = $form->getData();
}
return $this->render(
'lead_partner_overview2.html.twig',
[
'form' => $form->createView()
]);
}
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('id', HiddenType::class)
->add('name', TextType::class);
}
$leadPartnerList is of type array.
What am i doing wrong/missing here?
Kind Regards
It seems your action overview soesn't return the lead_partners variable you use in your template.
You can try to do this
return $this->render(
'lead_partner_overview2.html.twig',
[
'form' => $form->createView(),
'lead_partners' => $leadPartnerList, // I gess that's the list you want to loop ?
]);
I have a variety of choice fields in a form I'm creating, but all of them are loaded from PHP arrays that are defined in code. createForm() is given an empty model. Each field, when rendered with form_row() in twig tacks on about 2 seconds to render, each, making the request take about 8 or 9 seconds, which is ridiculous. I've tried searching, reading, etc. and from what I can tell I am following best practices. There are no database queries being run. Please help me nail down this huge performance problem.
Let's start with the form type:
class SubscriptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('latitude', 'hidden')
->add('longitude', 'hidden')
->add('crop', 'choice', [
'choices' => Crop::getFormChoices(),
'required' => true,
])
->add('infliction', 'choice', [
'choices' => Infliction::getFormChoices(),
'required' => true,
])
->add('emergenceDate', 'date')
->add('threshold', 'choice', [
'choices' => Threshold::getFormChoices(),
'label' => 'Severity threshold',
'required' => true,
])
->add('save', 'submit', ['label' => 'Subscribe']);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'PlantPath\Bundle\VDIFNBundle\Entity\Subscription',
]);
}
public function getName()
{
return 'subscription';
}
}
Then, in my controller:
public function formAction(Request $request)
{
$subscription = new Subscription();
$form = $this->createForm(new SubscriptionType(), $subscription);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// ...
}
return $this->render('PlantPathVDIFNBundle:Subscription:form.html.twig', [
'form' => $form->createView(),
]);
}
Subscription/form.html.twig:
{{ form_start(form, {'attr': {'id': 'subscription-form'}}) }}
{{ form_row(form.crop) }}
{{ form_row(form.infliction) }}
{{ form_row(form.emergenceDate) }}
{{ form_row(form.threshold) }}
{{ form_end(form) }}
As I mentioned, each call to form_row() takes a good two seconds. If I take out all the form_row()s and the form_end(), the template is rendered (without a form) in milliseconds. I cannot fathom why Symfony needs over 8 seconds to render several lines of HTML for a blank form.
The blocks for rendering the choice fields in bootstrap_3_layout.html.twig contains '|trans' filter which tries to find translation for each option.
Override choice_widget_options block in your twig file, if you do not wish to translate. This should eliminate the warning messages caused due to translation.