Symfony forms: wrong error mapping for embedded form - php

I'm using Symfony2 forms with silex..
I create my form like this:
$form = $app->form()
->add('foo', new FooType())
// ->add(...)
->add('save', 'submit')
->getForm();
FooType:
<?php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('bar', 'text', ['constraints' => new Assert\NotBlank()]);
$builder->add('baz', 'text', ['required' => false]);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'error_bubbling' => false,
]);
}
public function getName()
{
return 'foo';
}
}
If the "bar" field is empty the error always is mapped to the "foo" base field. Why? And how can I map the error to the field itself ("bar")?
Edit:
The form is rendered like this (Twig):
{{ form(form) }}
Result after submit with empty fields:
<form name="form" method="post" action="">
<div>
<label class="required">Foo</label>
<ul>
<li>This value should not be blank.</li>
</ul>
<div id="form_foo">
<div>
<label for="form_foo_bar" class="required">Bar</label>
<input type="text" id="form_foo_bar" name="form[foo][bar]" required="required">
</div>
<div>
<label for="form_foo_baz">Baz</label>
<input type="text" id="form_foo_baz" name="form[foo][baz]">
</div>
</div>
</div>
<div>
<button type="submit" id="form_save" name="form[save]">Save</button>
</div>
<input type="hidden" id="form__token" name="form[_token]" value="faebf2a0b0a05ddb8e5f40d58c7a3bf3d49522d4">
</form>

Related

Using the same form multiple times with different id

In my case, i would like to add for each application OneToMany links of documentation with the same form who contain two inputs, one for the name of the documentation and the second is for the link address documentation, the problem is that it work perfectly only for the first application in the list, but if i try to add a name and link address for the second application on the list or even the third .., when I submit my form, Symfony does not save the information in the database (without error message).
By the way i'm using modals to open the form.
As a beginner and in my first steps with symfony, i tried to read the documentation but i couldn't find a solution to resolve my problem, please find below the related code, thanks!
index.html.twig
{%for applications in applications %}
.....
<div class="tab-pane fade" id="dropdown-kv-21-1{{applications.id}}">
<p>Ajout de liens</p>
{{ form_start(form) }}
<div class="form_group">
<label for="{{form.nom.vars.id}}">Nom</label>
<input type="text" class="form_control" id="{{form.nom.vars.id}}"name={{form.nom.vars.full_name}}" value="{{form.nom.vars.value}}">
{{form_errors(form.nom)}}
{% do form.nom.setRendered %}
</div>
<div class="form_group">
<label for="{{form.lien.vars.id}}">Lien</label>
<input type="text" class="form_control" id="{{form.lien.vars.id}}" name="{{form.lien.vars.full_name}}" value="{{form.lien.vars.value}}">
{{form_errors(form.lien)}}
{% do form.lien.setRendered %}
</div>
<div class="form_group">
<select id="{{form.application.vars.id}}" class="form-control" name="{{form.application.vars.full_name}}" >
<option value="{{applications.id}}">{{applications.id}}</option>
</select>
{{form_errors(form.application)}}
{% do form.application.setRendered %}
</div>
<input type="submit" class="btn btn-success" value="Ajouter" style="transform: translate(27em);"id="carto_cartographiebundle_liendocapp_ajouter"name="carto_cartographiebundle_liendocapp[ajouter]">
{% do form.ajouter.setRendered %}
{{form_end(form)}}
{%endfor%}
AcceuilController
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$applications = $em->getRepository('CartoBundle:Application')->findAll();
if($request->isMethod('POST')){
$nomApp = $request->get('nomApp');
$applications = $em->getRepository('CartoBundle:Application')->findBy(array("nomApp"=>$nomApp));
}
$lienDocApp = new LienDocApp();
$form=$this->createForm(LienDocAppType::class, $lienDocApp);
$form->handleRequest($request);
if ($form->isSubmitted()&& $form->isValid()){
$em = $this->getDoctrine()->getManager();
$em->persist($lienDocApp);
$em->flush();
}
return $this->render('CartoBundle:Accueil:index.html.twig', array(
'applications'=>$applications,
'lienDocApp' => $lienDocApp,
'form' => $form->createView(),
));
}
LienDocAppType
class LienDocAppType extends AbstractType{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom')
->add('lien')
->add('application')
->add('ajouter', SubmitType::class);
}/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Carto\cartographieBundle\Entity\LienDocApp'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'carto_cartographiebundle_liendocapp';
}
}
i finally found the solution for my problem and i'm posting it here maybe it will be helpful form some one,
actually there is a hidde, input named _token, all i did is changing the Id of this input with the id of the chosen application
<input type="hidden" id="{{ form._token.vars.id }}" name="{{form._token.vars.full_name}}" value="{{ form._token.vars.value }}">
will be :
<input type="hidden" id="{{ applications.id }}" name="{{form._token.vars.full_name}}" value="{{ form._token.vars.value }}">
cheers !

CSRF Invalid because of two entities in the registration process

I have two entities and one form for the registration process.When I add csrf token it probably works for one of them, not for both.
<form action="" method="post" class="registerForm" id="registerForm" novalidate>
<div class="form-row">
<div class="form-group col-lg-6">
<input type="text" class="form-control inner-blue-shadow" name="company[name]" id="companyName" placeholder="Име на фирма" required/>
<div class="invalid-feedback"></div>
<div class="valid-feedback"></div>
</div>
<div class="form-group col-lg-6">
<input type="text" class="form-control inner-blue-shadow" name="company[bulstat]" id="bulstat" placeholder="Булстат" required/>
<div class="invalid-feedback"></div>
<div class="valid-feedback"></div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control inner-blue-shadow" name="user[username]" id="username" placeholder="Потребителско име" required/>
<div class="invalid-feedback"></div>
<div class="valid-feedback"></div>
</div>
<div class="form-group">
<input type="email" class="form-control inner-blue-shadow" name="user[email]" id="email" placeholder="Имейл адрес" required/>
</div>
<div class="form-group">
<input type="password" class="form-control inner-blue-shadow" name="user[password]" id="password" placeholder="Парола" required/>
<div class="invalid-feedback"></div>
<div class="valid-feedback"></div>
</div>
{{ form_row(form._token) }}
<div class="text-center"><button type="submit" id="registerButton">Create account</button></div>
</form>
The output is: "user__token" so as far as I can see it generates it with the exception that is affects only one of the two entities - User and it does not work for the other one.
When I remove the company fields everything works like a charm.
The forms are:
1.User
public function buildForm(FormBuilderInterface $builder, array $options)
{
$schools = $this->schoolRepository->findAll();
$builder
->add('username', TextType::class)
->add('email', EmailType::class)
->add('password', PasswordType::class)
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\User',
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'task_item'
));
}
2.Company:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class);
$builder->add('bulstat', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Company',
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'task_item'
));
}
The controller where forms are being created:
$company = new Company();
$companyForm = $this->createForm(CompanyType::class, $company);
$companyForm->handleRequest($request);
$user = new User();
$userForm = $this->createForm(UserType::class, $user);
$userForm->handleRequest($request);
How could I fix it?
This is basically a question of how to combine two entities into one form. There are other questions that address this but some are a bit old and out of date. This question is also an example of not taking advantage of what Symfony has to offer and working a bit harder than the developer has to.
Specifically, Symfony deals with csrf out of the box and requires no special configuration. You can do more stuff with it but it is easier to start with a default working implementation and then tweak if necessary. In a similar fashion, twig has a default rendering of a form. After the form is working you can then use various styling tools to make it look pretty.
Start by simplifying your EntityType classes and adding a RegisterType:
class UserType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class)
->add('email', EmailType::class)
->add('password', PasswordType::class);
}
}
class CompanyType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Company::class,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('bulstat', TextType::class);
}
}
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', UserType::class)
->add('company', CompanyType::class)
->add('register', SubmitType::class);
}
}
Now adjust the controller code and use the default form rendering:
public function register(Request $request)
{
$user = new User();
$company = new Company();
$register = ['user' => $user, 'company' => $company];
$form = $this->createForm(RegisterType::class, $register);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($user);
dump($company);
}
return $this->render('register.html.twig',['form' => $form->createView()]);
}
# register.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<h1>Register</h1>
{{ form(form) }}
{% endblock %}
At this point you should have a working form. You can then add validation and persistence as well as making the form look nice.

Laravel 6 one --> many relationship in resource controller

I currently have an application that can have a modifier with many options. An example use case is a modifier of Toppings with options of cheese, lettuce, salt, and pepper. A modifier can have 1 --> N options.
I want to have a form that displays the Modifier name and also allows the ability to edit/delete Option records at same time. I want to embed this in my current form for adding/editing modifiers.
Is there a simple way to do this so I can have just one Modifier resource controller that also manages Options?
web.php
Route::resource('modifiers', 'ModifierController')->middleware('auth');
Controller:
namespace App\Http\Controllers;
use App\Modifier;
use Illuminate\Http\Request;
class ModifierController extends Controller
{
public function create()
{
$data = ['action' => route('modifiers.store'),'method' => 'POST', 'modifier' => new Modifier()];
return view('modifiers.form',$data);
}
public function store(Request $request)
{
$modifier = new Modifier($request->all());
$this->do_validate($request);
$modifier->save();
return redirect(route('modifiers.index'));
}
}
views/modifiers/form.blade.php
#extends('layouts.layout-2')
#section('content')
<div class="container">
<div class="row justify-content-center">
#include ('layouts.errors')
<div class="col-md-12">
{!! Form::open(['url' => $action, 'method' => 'post', 'class' => 'form', 'id' => 'form'])!!}
#csrf
#method($method)
<div class="form-group">
<label for="name">{{ __('modifiers.name')}}</label>
<input type="text" class="form-control" id="name" name="name" value = "{{old('name',$modifier->name)}}">
</div>
<button id="btnSubmit" class="btn btn-primary">{{ __('common.submit') }}</button>
{!! Form::close() !!}
</div>
</div>
</div>
#endsection
Models:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Modifier extends Model
{
use SoftDeletes;
protected $guarded = ['id'];
public function options()
{
return $this->hasMany('App\Option');
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Option extends Model
{
use SoftDeletes;
protected $guarded = ['id'];
public function modifier()
{
return $this->belongsTo('App\Modifier');
}
}
I think you can use a select element:
<select name="options[]" id="options" class="form-control">
#foreach($options as $option)
<option value="{{ $option->id }}" {{ $modifier->options->contains('id', $option->id) ? 'selected' : '' }}>{{ $option->name }}</option>
#endforeach
</select>
Hope it helps.
Then, within some controller:
public function update(Request $request, $id)
{
$modifier = Modifier::find($id);
$modifier->options()->sync($request->input('options'));
// Do whatever more you want to do
}

Symfony 3.1 Plain html forms Erros

I am using symfony 3.1 with regular html forms to make it easier to do front-end styling. I am continuously getting the following two errors. the first is;
The CSRF token is invalid. Please try to resubmit the form.
and the second is
This form should not contain extra fields.
The first is because I don't know how to correctly use the csrf token with plain html forms. My form looks like this:
FormType Class:
class TypeFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('isActive', CheckboxType::class)
->add('descriptor');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Type',
'csrf_protection' => true,
//'allow_extra_fields' => true
]);
}
public function getName()
{
return 'app_bundle_type_form_type';
}
public function getBlockPrefix()
{
// removes the need for the form[$name] requirement for form inputs
return '';
}
}
html form:
<form method="post">
<fieldset class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name">
</fieldset>
<fieldset class="form-group">
<label for="descriptor">Descriptor</label>
<select class="form-control c-select" id="descriptor" name="descriptor">
<option value="Award">Award</option>
<option value="Donation">Donation</option>
<option value="Event">Event</option>
</select>
</fieldset>
<fieldset class="checkbox">
<label class="" for="isActive">
<input type="checkbox" id="isActive" name="isActive" checked> Active</label>
</fieldset>
<input type="hidden" name="_csrf_token" value="{{ csrf_token("app_bundle_type_form_type") }}">
<button type="submit" class="btn btn-primary-outline pull-right">Submit</button>
</form>
Controller:
/**
* #Route("/admin/types/new", name="admin_types_new")
*/
public function newAction(Request $request)
{
$form = $this->createForm(TypeFormType::class);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
dump($form->getData());die;
}
return $this->render('admin/type/new.html.twig');
}
Can anyone tell me what I am doing wrong?
There is better (and working) approach to form rendering in Symfony:
In your controller:
/**
* #Route("/admin/types/new", name="admin_types_new")
*/
public function newAction(Request $request)
{
$form = $this->createForm(TypeFormType::class);
// ...
return $this->render('admin/type/new.html.twig', array('form' => $form->createView());
}
In your template:
{{ form_start(form, {'action': path('admin_types_new'), 'method': 'POST'}) }}
{{ form_widget(form) }}
<input type="submit" />
{{ form_end(form) }}
Please try this because it is usually a better approach: you don't have to care whether your html which you put by hand is correct and all ids and attributes are present or if csrf is generated properly. All is done by Symfony in this case, you just need to modify underlying FormType class and twig template to shape fields of your form.
More on the topic:
http://symfony.com/doc/current/book/forms.html
http://symfony.com/doc/current/reference/forms/twig_reference.html
http://symfony.com/doc/current/cookbook/form/form_customization.html
If anyone is interested in my fix:
<input type="hidden" name="{{ form._token.vars.full_name }}" value="{{ form._token.vars.value }}" />
It seems that you need name your token field as '_token'

Wrap Symfony form fields in a div in Twig

Given the simple form build:
$form = add('a')
->add('b')
->add('c')
->add('d')
->add('e')
->add('f');
I'd like to wrap a div tag around ABC and DEF, like so:
<div class="section1">
<input type="text" name="a" />
<input type="text" name="b" />
<input type="text" name="c" />
</div>
<div class="section2">
<input type="text" name="d" />
<input type="text" name="e" />
<input type="text" name="f" />
</div>
Problem is, I am just able to use the Symfony Form Component for this project. Is there a way with twig to render the form fields in groups like above? I will need to specify logic that says something simliar to "start section 1 with 'a' and section 2 with 'd'", so this way if any fields change inbetween (say we remove the field with the name 'b') the form will still work.
My twig file looks like this, which is obviously not correct:
<form action="#" method="POST" {{ form_enctype(form) }}>
{% for child in form.children %}
<div class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endfor %}
</form>
The best way to do it I think is building the form outside the controller (as a class, extending AbstracType)
So, you will need these files:
Your controller (as DefaultController.php)
Your twig template (as index.html.twig)
Your main form (MyFormType.php in this example)
One form for each section (in your example Section1Type.php and Section2Type.php)
So, the idea is building one single form (MyFormType) made by many individual section forms (Section1Type and Section2Type). Then calling this main form in your controller and rendering it in your template (with a "for" loop for each section).
So here you have the code:
Your controller:
<?php
# /src/Acme/DefaultBundle/Controller/DefaultController.php
namespace Acme\DefaultBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\DefaultBundle\Form\MyFormType;
class DefaultController extends Controller
{
public function indexAction()
{
$form = $this->createForm(new MyFormType());
return $this->render('AcmeDefaultBundle:Default:index.html.twig', array(
'form' => $form->createView()
));
}
}
Your template:
{# /src/Acme/DefaultBundle/Resources/views/Default/index.html.twig #}
{{ form_start(form) }}
<div class="section1">
{% for input in form.section1 %}
{{ form_label(input) }}
{{ form_widget(input) }}
<br>
{% endfor %}
</div>
<div class="section2">
{% for input in form.section2 %}
{{ form_label(input) }}
{{ form_widget(input) }}
<br>
{% endfor %}
</div>
{{ form_end(form) }}
The main form:
<?php
# /src/Acme/DefaultBundle/Form/MyFormType.php
namespace Acme\DefaultBundle\Form;
use Acme\DefaultBundle\Form\Section1Type;
use Acme\DefaultBundle\Form\Section2Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// This calls your section forms as many as you need
$builder->add('section1', new Section1Type());
$builder->add('section2', new Section2Type());
$builder->add('Send', 'submit');
}
public function getName()
{
return 'myform';
}
}
The Section1 form:
<?php
# /src/Acme/DefaultBundle/Form/Section1Type.php
namespace Acme\DefaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class Section1Type extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('a', 'text');
$builder->add('b', 'text');
$builder->add('c', 'text');
}
public function getName()
{
return 'section1';
}
}
And Section2 form:
<?php
# /src/Acme/DefaultBundle/Form/Section2Type.php
namespace Acme\DefaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class Section2Type extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('d', 'text');
$builder->add('e', 'text');
$builder->add('f', 'text');
}
public function getName()
{
return 'section2';
}
}
And that's it, you can edit each individual section (adding and removing inputs) and you will get all of them in your template without modifying it.

Categories