Symfony4 Form not submitted - forms

I have a problem with my Form in symfony4, when I click on the submit button it is not submitted (It doesn't pass the isSubmitted condition)
Here is my Form
<?php
namespace App\Form\Backend\Team;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Class AddTeamForm
* #package App\Form\Backend\Team
*/
class TeamForm extends AbstractType
{
const NAME = 'name';
const WEBSITE = 'website';
const IRC = 'irc';
const DISCORD = 'discord';
const TWITTER = 'twitter';
const FACEBOOK = 'facebook';
const HIDDEN = 'hidden';
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add(self::NAME, TextType::class, [
'required' => true,
'label' => 'admin.team.form.name'
])
->add(self::WEBSITE, TextType::class, [
'required' => false,
'label' => 'admin.team.form.website'
])
->add(self::IRC, TextType::class, [
'required' => false,
'label' => 'admin.team.form.irc'
])
->add(self::DISCORD, TextType::class, [
'required' => false,
'label' => 'admin.team.form.discord'
])
->add(self::TWITTER, TextType::class, [
'required' => false,
'label' => 'admin.team.form.twitter'
])
->add(self::FACEBOOK, TextType::class, [
'required' => false,
'label' => 'admin.team.form.facebook'
])
->add(self::HIDDEN, CheckboxType::class, [
'required' => false,
'label' => 'admin.team.form.hidden'
]);
}
}
and Here is my action
/**
* #Route("/add", name="add")
*/
public function add(Request $request)
{
$form = $this->createForm(TeamForm::class);
$form->handleRequest($request);
if ($form->isSubmitted()) {
var_dump('submitted');
if($form->isValid())
{
} else {
var_dump('non vlide');exit;
}
$team = $form->getData();
}
return $this->render('backend/team/add.html.twig', [
'form' => $form->createView(),
]);
}
and my view
{{ form_start(form, {attr: {class: 'ui form inverted', novalidate:'novalidate', 'action': url('admin_team_add')} }) }}
{{ form_errors(form) }}
<div class="ui grid two column">
<div class="row">
<div class="twelve wide column">
{{ form_row(form.name) }}
{{ form_row(form.website) }}
{{ form_row(form.irc) }}
{{ form_row(form.discord) }}
{{ form_row(form.twitter) }}
{{ form_row(form.facebook) }}
<div class="field">
<div class="ui checkbox">
{{ form_widget(form.hidden) }}
{{ form_label(form.hidden) }}
{{ form_errors(form.hidden) }}
</div>
</div>
<input type="submit" class="ui button green" value="{{ 'admin.team.form.btn-create' }}"/>
</div>
</div>
</div>
{{ form_end(form) }}
Like I said, When I click my submit button, the var_dump 'submitted' is not shown. The page refreshes but nothing happen, like a normal page refresh.
I must miss something but can't figure what ...

you can check if your route name exist:
php bin/console debug:router admin_team_add
if it's not exist , change your view action to :
{{ form_start(form, {attr: {class: 'ui form inverted', novalidate:'novalidate', 'action': url('add')} }) }}
or change your controller route name to :
/**
* #Route("/add", name="admin_team_add")
*/
public function add(Request $request)
{

Related

Symfony form CollectionType render the label only once

I have a SquadType form which contains an embedded SquadMemberType form as a collectionType field.
Each time I press the add squad member button on my form, I can add a squadMember form from the prototype. Works perfectly.
The fact is, each time I add a squadMember form, it displays the name and tag label. I want to find a way to display these labels only for the first squadMember but not for the others.
I tried to set label to false in the form squadMemberType class and to display it directly in the template with no success (didn't find a way to make it work).
Is there a way to achieve that ?
My main form :
class squadType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('squadName', TextType::class)
->add('squadDescription', TextareaType::class)
->add('squadMembers', CollectionType::class, [
'label' => ' ',
'entry_type' => SquadMemberType::class,
'entry_options' => [
'attr' => [
'class' => 'email-box',
'style' => 'display:flex; gap:1rem;',
]
],
'allow_add' => true,
])
->add(
'save',
SubmitType::class, ['label' => 'Create Squad']
)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Squad::class,
]);
}
}
My embedded form :
class SquadMemberType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('squadMemberName', TextType::class, [
'label_attr' => [
'class' => 'formLabel',
],
'label' => 'Name',
])
->add('squadMemberTag', IntegerType::class, [
'label' => 'Tag',
// 'label' => false,
'label_attr' => [
'class' => 'formLabel',
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => SquadMember::class,
]);
}
}
My twig :
<div class="content">
{{ form_start(form, {'attr': {'class': 'form-horizontal', 'id': 'create-squad'}}) }}
<div class="creation-form">
<fieldset class="creation-form-globalInfo">
<div class="form-group {{ form.squadName.vars.valid==false ? "has-error" }}">
{{ form_label(form.squadName, null, {
'label': 'Squad Name',
'label_attr': {'class': 'control-label'}}) }}
<div class="">
{{ form_widget(form.squadName) }}
{{ form_errors(form.squadName) }}
</div>
</div>
<div class="form-group {{ form.squadDescription.vars.valid==false ? "has-error" }}">
{{ form_label(form.squadDescription, null, {
'label': 'Squad Description',
'label_attr': {'class': 'control-label'}}) }}
<div class="">
{{ form_widget(form.squadDescription) }}
{{ form_errors(form.squadDescription) }}
</div>
</div>
</fieldset>
<fieldset class="creation-form-info">
<ul id="squadMember-fields-list"
data-prototype="{{ form_widget(form.squadMembers.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ form.squadMembers|length }}"
class="squad-form">
{% for squadMemberField in form.squadMembers %}
<li>
{{ form_errors(squadMemberField) }}
{{ form_widget(squadMemberField) }}
</li>
{% endfor %}
</ul>
<button type="button"
class="add-another-collection-widget secundary-button"
data-list-selector="squadMember-fields-list">
{{ 'squad.add.squadMember.button.label'|trans }}
</button>
</fieldset>
</div>
<div class="validation-bottom">
{{ form_row(form.save,{
'label': 'squad.add.confirm.button',
'attr': {
'class': 'primary-button'
}
})
}}
</div>
{{ form_end(form) }}
</div>
my script :
{% block javascripts %}
<script>
$(document).ready( function () {
$('.add-another-collection-widget').click(function (e) {
var list = jQuery(jQuery(this).attr('data-list-selector'));
// Try to find the counter of the list or use the length of the list
var counter = list.data('widget-counter') || list.children().length;
// grab the prototype template
var newWidget = list.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, counter);
// Increase the counter
counter++;
// And store it, the length cannot be used if deleting widgets is allowed
list.data('widget-counter', counter);
// create a new list element and add it to the list
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});
</script>
{% endblock %}

Subform values are showing up as null on the receiving end after form submit

I have built out a form which submits fine, however subform values all end up as null on the receiving end when I look at them in the controller.
Here is my UserProfileType form, based on the Userclass. So specifically, the subforms we are looking at are subscriptionTier1, subscriptionTier1, and subscriptionTier1:
class UserProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('email', EmailType::class)
// etc... I'll keep out the unimportant fields
// here are the subforms whose values show up as null on the back end
->add('subscriptionTier1', UserSubscriptionTierType::class, [
'required' => false,
'mapped' => false
])
->add('subscriptionTier2', UserSubscriptionTierType::class, [
'required' => false,
'mapped' => false
])
->add('subscriptionTier3', UserSubscriptionTierType::class, [
'required' => false,
'mapped' => false
])
->add('save', SubmitType::class, [
'attr' => ['class' => 'save'],
])
;
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
'mode' => null
)
);
}
}
Here is what my UserSubscriptionTierType form type class looks like:
class UserSubscriptionTierType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [
'attr' => [
'maxlength' => 25
]
])
->add('price', ChoiceType::class, [
'choices' => [
'$10 per month' => 10,
'$20 per month' => 20,
'$30 per month' => 30
]
])
->add('description', TextareaType::class)
->add('messaging', CheckboxType::class, [
'required' => false
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => UserSubscriptionTier::class,
));
}
}
As you can see in the picture below, the subform subscriptionTier1's name, price, and description values are all set on the front-end:
Here is what the twig code looks like for the subscriptionTier1 subform:
<div class="row justify-content-center w-100 ml-0 mr-0 mt-3 tier tier-1" >
<div class="col-lg-6 mt-1">
<div class="form-group form-control-lg w-100">
<label for="firstname" class="mb-2">Tier 1 Name</label>
{{ form_widget(form.subscriptionTier1.name, { 'attr': {'class': 'form-control'}}) }}
</div>
</div>
<div class="col-lg-6 mt-1">
<div class="form-group form-control-lg w-100">
<label for="tier1price" class="mb-2">Tier 1 Price</label>
{{ form_widget(form.subscriptionTier1.price, { 'attr': {'class': 'form-control'}}) }}
</div>
</div>
<div class="col-lg-12 mt-3">
<div class="form-group form-control-lg w-100">
<label for="bio" class="mb-2">Tier 1 Description
<sup class="text-danger">*</sup>
</label>
{{ form_widget(form.subscriptionTier1.description, { 'attr': {'class': 'form-control border-box', 'rows':'8'}}) }}
</div>
</div>
<div class="col-lg-12">
<div class="form-group form-control-lg w-100">
<div class="form-check">
<label class="form-check-label">
{{ form_widget(form.subscriptionTier1.messaging, { 'attr': {'class': 'form-control form-check-input'}}) }}
<span class="form-check-sign"></span>
Enable Messaging
</label>
</div>
</div>
</div>
</div>
Here is the code on the receiving end, the controller:
public function saveProfileAction(Request $request)
{
$user = $this->getUser();
$form = $this->createForm(UserProfileType::class, $user);
$form->handleRequest($request);
dump($form->get('subscriptionTier1')->getData());
So in debugging, if I dump just the first form subscriptionTier1, you can see that the values are all null.
ProfileController.php on line 269:
Form {#2235 ▼
-config: FormBuilder {#2236 ▶}
-parent: Form {#2112 ▶}
-children: OrderedHashMap {#2237 ▶}
-errors: []
-submitted: true
-clickedButton: null
-modelData: UserSubscriptionTier {#2280 ▼
-id: null
-posts: ArrayCollection {#2323 ▶}
-subscriptions: null
-name: null // Don't understand why this is null
-price: null // Don't understand why this is null
-description: null // Don't understand why this is null
-tierNumber: null
-versionNumber: null
-messaging: false
-user: null
+"subsciptions": ArrayCollection {#2089 ▶}
}
-normData: UserSubscriptionTier {#2280 ▶}
-viewData: UserSubscriptionTier {#2280 ▶}
-extraData: []
-transformationFailure: null
-defaultDataSet: true
-lockSetData: false
}
Would anyone know why the values are not passing to the back-end (or getting nulled out)?
Since the data_class option of UserProfileType form type is set to User class, the model data will be an instance of User class and since the User class doesn't have fields like subscriptionTier1 etc, these will not appear in your model data.
Instead, you could access unmapped fields in a form in a controller like this:
$subscriptionTier1 = $form->get('subscriptionTier1')->getData();
Documentation here
EDIT: You can only access the value of subscriptionTier1 etc, only after the form has been handled and only if the form has been submitted, otherwise it will be null:
public function saveProfileAction(Request $request)
{
$user = $this->getUser();
$form = $this->createForm(UserProfileType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$subscriptionTier1 = $form->get('subscriptionTier1')->getData();
dump($subscriptionTier1);
}
I don't know if this is just a workaround or of this is actually the best practices way to do it, but here's how I got it to work:
I had the following subform tags in:
{{ form_start(form.subscriptionTier1) }}
etc...
{{ form_end(form.subscriptionTier1) }}
This will nest form tags. Apparently you are not allowed to nest forms like this.
So taking out the {{ form_start(form.subscriptionTier1) }} , {{ form_end(form.subscriptionTier1) }} for all the subscriptionTier1, subscriptionTier1, subscriptionTier1and then checking for the form values in the request object worked.

Symfony submit button doesn't flush/update the entity properly

My entity "corporation" has two ManyToMany relationships with
entity "User" and
entity "Agency"
The related data, I'd like to display as a table with a EntityType-dropdown to (un-)select data.
Therefor I have the following form:
$builder
->add('usercorporations', EntityType::class, array(
'class' => 'UserBundle:User',
'query_builder' => function (EntityRepository $er) use ($corporationMarket) {
return $er->createQueryBuilder('e')
->andWhere(':marketIds MEMBER OF e.markets')
->setParameter('marketIds',$corporationMarket)
->orderBy('e.lastName', 'ASC');
},
'choice_label' => function($user) {
return $user->getFirstName() . ' ' . $user->getLastName() ;
},
'expanded' => false, 'multiple' => true,
'required'=>false,
'by_reference' => false,
'empty_value' => "label.select_user",
'translation_domain' => 'Agency'))
->add('agencies', EntityType::class, array(
'class' => 'AppBundle:Agency',
'query_builder' => function (EntityRepository $er){
return $er->createQueryBuilder('a')
->addOrderBy('a.account', 'ASC');
},
'label' => 'label.agencies',
'choice_label' => function($agency) {
return $agency->getId() . ' - ' . $agency->getAccount() . ' - ' . $agency->getCityName() . ', ' . $agency->getState();
},
'expanded' => false, 'multiple' => true,
'required' => false,
'translation_domain' => 'Agency',
));
my twig template includes the two dropdowns as well as two tables for each of the entities.
{{ form_start(form) }}
{# This renders a red banner if the form contains errors.
If the form variable is not called "form", pass it explicitly. #}
{% include 'Form/form_errors_banner.html.twig' %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<legend>{{ 'label.assignedUsers'|trans }}</legend>
{{ form_widget(form.usercorporations) }}
<br>
<br>
{% include 'UserBundle:User:AssignedUsers.html.twig' %}
</div>
</div>
</div>
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<legend>{{ 'label.assignedAgencies'|trans }}</legend>
{{ form_widget(form.agencies) }}
<br>
<br>
{% include 'AppBundle:Agency:AssignedAgencies.html.twig' with {'entity': corporation} %}
</div>
</div>
</div>
<div>
<input type="submit" class="btn-primary btn" value="{{ 'label.apply'|trans }}"/>
</div>
{{ form_end(form) }}
</div>
I'm using the tables (AssignedUsers and AssignedAgencies) on other pages too, that's all working fine.
So now my problem:
I'd like to have the possibility to simply add and remove users or agencies. By just deselecting e.g. a user from the dropdown and then submitting, it's working, the user isn't assigned to the corporation anymore. Somehow, the same procedure is not working for the agencies. I don't know why though, I dumped the data prior to and after the flush and it's dumping the correct data, but when I return to the corporation, nothing changed.
My controller action:
/**
* #Route("/corporation/{id}/{page}", name="appBundle_corporation", requirements={"id" = "\d+", "page" = "\d+"}, defaults={"page" = 1}))
*/
public function corporationAction($id, Request $request, $page)
{
$repository = $this->getDoctrine()
->getRepository('AppBundle:Corporation');
$em = $this->getDoctrine ()->getManager ();
$corporation = $repository->findOneById($id);
/*
* CREATE PAGINATION FOR TABLES
*/
//assigned Users
$assignedUsers = $corporation->getUsercorporations();
$assignedAgencies = $corporation->getAgencies();
dump($assignedAgencies);
$form = $this->createForm(CorporationAssignmentType::class,$corporation, array(
'corporationMarket' => $corporationMarket,
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($corporation);
$em->flush();
dump($assignedAgencies);
$this->addFlash(
'success',
'Your changes were saved!'
);
return $this->redirectToRoute('appBundle_corporation', array('id' => $corporation->getId()));
}
return $this->render('AppBundle::corporation.html.twig', array(
'item' => $corporation,
'corporation'=>$corporation,
'assignedUsers'=>$userTable,
'assignedAgencies' => $agencyTable,
'form' => $form->createView(),
));
}
any ideas why it's flushing the users but not the agencies?
I solved my problem that way, but it might be that #ASOlivieri 's answer could've helped es well.
What I did initially is:
in Entity Agency
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Corporation", inversedBy="agencies", cascade={"persist"})
* #ORM\JoinTable(name="app_agency_corporations",
* joinColumns={#ORM\JoinColumn(name="agency_id", referencedColumnName="iata8")},
* inverseJoinColumns={#ORM\JoinColumn(name="corporation_id", referencedColumnName="ucid")})
* #var \AppBundle\Entity\Corporation
**/
private $corporations;
in Entity Corporation
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Agency", mappedBy="corporations")
**/
private $agencies;
when switching it --> the shorter code to the Agency's property corporations and the longer code about the JoinTable etc. to the Corporation's property agencies it worked.
If anybody could explain to me why this is the solution, I'd be happy!
If your entities work in other places (other controllers, etc.) then I suspect you have to by_reference to false in your agencies form options.
->add('agencies', EntityType::class, array(
'class' => 'AppBundle:Agency',
'query_builder' => function (EntityRepository $er){
return $er->createQueryBuilder('a')
->addOrderBy('a.account', 'ASC');
},
'label' => 'label.agencies',
'choice_label' => function($agency) {
return $agency->getId() . ' - ' . $agency->getAccount() . ' - ' . $agency->getCityName() . ', ' . $agency->getState();
},
'expanded' => false, 'multiple' => true,
'required' => false,
'by_reference' => false,
'translation_domain' => 'Agency',
));
It looks as though it's set that way in your user form options.

I can not display the password field input (with 'repeat' option) with twig (Symfony2)

I have the following form and Twig template:
FormType
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text',array('label' => 'Name', 'max_length' => 30))
->add('surname','text',array('label' => 'Sjurname', 'max_length' => 30))
....
->add('password', 'repeated', array('error_bubbling' => true,'required' => false, 'first_options' => array('label' => 'New Password'),'second_options' => array('label' => 'Repeat Password'),'type' => 'password' ,'invalid_message'=> 'Passwords must be the same.'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bundle\Entity\User',
));
}
/**
* #return string
*/
public function getName()
{
return 'user';
}
}
Twig
....
{{form_start(form, {'method':'Put','attr':{'data-toggle':"validator", 'role':"form" ,'novalidate':'novalidate', 'autocomplete':'off'}})}}
{{ form_errors(form) }}
<div class="form-group has-feedback">
{{ form_label(form.password, 'New Password:',{'label_attr':{'class': 'col-md-5'}}) }}
<div class="col-md-7">
{{ form_widget(form.password,{'attr':{'class': 'form-control', 'type': 'password','data-error': 'You must enter a minimum of 6 characters', 'placeholder': 'Min 6 characters', 'data-minlength':"6", 'required':'true' }}) }}
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
<div class="help-block with-errors"></div>
</div>
</div>
{{ form_row(form.submit,{'attr':{'class': 'btn btn-primary pull-right' }}) }}
<div class="hidden">
{{ form_rest(form) }}
</div>
{{ form_end(form) }}
...
The problem I have is that the input is not created. The label is displayed. I have tried using form.password.first and form.password.second but neither is the input (only the labels). With the other fields I do not have that problem, the only thing in this case all the others are hidden in a div. What could be the problem?
I appreciate your help.
Take a look at this. it is for symfony 2.8/3+ but you can adapt it to your needs.
FormType
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => [
'label' => 'registration.password',
],
'second_options' => [
'label' => 'registration.retype.password',
],
]) ...
twig
<div>
{{ form_label(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.first) }}
{{ form_errors(form.plainPassword.first) }}
</div>
<div>
{{ form_label(form.plainPassword.second) }}
{{ form_widget(form.plainPassword.second) }}
{{ form_errors(form.plainPassword.second) }}
</div>
Do you have a version of xDebug installed? You could place a breakpoint on the return section of the Controller and have a look inside the children element of the form. This could tell you what the names of the elements are.
**PS, I would have commented this but I don't have enough rep.

Symfony2 : form_row prototype in a loop

I have an Entity Product to which I'm adding traductions (collection of ProductDescription).
Here is my ProductDescriptionType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('description', 'textarea')
->add('metaDescription', 'textarea')
->add('metaKeyword', 'textarea')
->add('tag')
->add('languageId', 'hidden')
;
}
And my ProductType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('upc')
->add('model')
->add('sku')
->add('quantity')
->add('price')
->add('status', 'choice',
array(
'choices' => Product::getStatusArray(),
'required' => true,
)
)
;
//ProductDescription
$builder->add('traductions', 'collection',
array(
'type' => new ProductDescriptionType(),
'allow_add' => true,
'prototype' => true,
'label' => false,
'by_reference' => false
)
);
}
And in my view new.html.twig, I'm doing :
{% for language in languages %}
<div role="tabpanel" class="tab-pane" id="product-traduction-{{ language.languageId }}">
{{ form_row(create_form.traductions.vars.prototype.languageId, {'value' : language.languageId }) }}
{{ form_row(create_form.traductions.vars.prototype.name) }}
{{ form_row(create_form.traductions.vars.prototype.description) }}
{{ form_row(create_form.traductions.vars.prototype.metaDescription) }}
{{ form_row(create_form.traductions.vars.prototype.metaKeyword) }}
{{ form_row(create_form.traductions.vars.prototype.tag) }}
</div>
{% endfor %}
I'm not looping through create_form.traductions because it's empty (creation of a product), and I would like to have the fields of the ProductDescription already showing for all languages available.
The problem is that form_row only render the fields once.
I saw that this part is explained on the CookBook http://symfony.com/doc/current/cookbook/form/form_collections.html, but it's done with JQuery and I have to do a lot of modification to the prototype inputs to achieve what I want (replace name, put languageId in the hidden input, etc).
Can someone help me please ?
Thanks for your time.