entityType can get and save data - forms

I've 4 entities Company, Network, Customer and Process.
I don't know why I use fucntion create and edit don't save process in my DB !
Cutomers Netwoks and Compny this saved well.
Perhaps I forget somethings.
Some one can help please.
Company Form `
<?php
namespace App\Form;
use App\Entity\Company;
use App\Entity\Process;
use App\Form\NetworkType;
use App\Form\CustomerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom',
'required' => 'true',
'attr' => [
'class' => 'mt-1',
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('phone', TelType::class, [
'required' => 'true',
'label' => 'Téléphone',
'attr' => [
'class' => 'mt-1',
'placeholder' => 'xx xx xx xx xx'
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('location', TextType::class, [
'label' => 'Ville',
'required' => 'true',
'attr' => [
'class' => 'mt-1'
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('customers', CollectionType::class, [
'entry_type' => CustomerType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false
])
->add('networks', CollectionType::class, [
'entry_type' => NetworkType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false
])
->add('processes', EntityType::class, [
'class' => Process::class,
// 'entry_options' => ['label' => false],
// 'allow_add' => true,
// 'allow_delete' => true,
// 'by_reference' => false,
'choice_label' => 'software',
'label' => 'process',
'multiple' => true,
// 'expanded' => true
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Company::class,
]);
}
}
Company View
{% extends 'base.html.twig' %}
{% block title %}
{{company.name}}
{% endblock %}
{% block body %}
<nav class="navbar navbar-expand bg-light navbar-light sticky-top px-4 py-0">
{% include "navigation/_nav-horizontal.html.twig" %}
</nav>
<div class="container-fluid pt-4 px-4">
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Process</h5>
{% for process in company.processes %}
{# {% for key in process.software %} #}
<p class="card-text">{{process.software}}</p>
{# {% endfor %} #}
{% endfor %}
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Architecture réseau</h5>
{% for network in company.networks %}
<h6>{{network.title}}</h6>
<p class="card-text">{{network.content}}</p>
{% if network.attachment %}
<p class="card-text">{{network.attachment}}</p>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Contacts</h5>
{% for customer in company.customers %}
<!-- Button trigger modal -->
<button type="button" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#{{customer.firstname}}">
{{customer.firstname}}
</button>
<!-- Modal -->
<div class="modal fade" id="{{customer.firstname}}" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">{{customer.firstname}}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="my-3">{{customer.firstname}}</div>
<div class="my-3">{{customer.lastname}}</div>
<div class="my-3">{{customer.mobile}}</div>
<div class="my-3">{{customer.email}}</div>
<div>
<a class="btn btn-primary" href="#" role="button">Modifier</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% include "partiel/_footer.html.twig" %}
{% endblock %}
Company Controller
<?php
namespace App\Controller;
use App\Entity\Company;
use App\Form\CompanyType;
use App\Repository\CompanyRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class CompanyController extends AbstractController
{
#[Route('/company', name: 'company', methods: ['GET'])]
public function index(CompanyRepository $repo, PaginatorInterface $paginator, Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$companies = $repo->findAll();
$companies = $paginator->paginate(
$companies, /* query NOT result */
$request->query->getInt('page', 1), /*page number*/
15 /*limit per page*/
);
return $this->render('company/index.html.twig', [
'companies' => $companies,
]);
}
#[Route('/company/add', name: 'company_add', methods: ['GET', 'POST'])]
#[Route('/company/edit/{slug}', name: 'company_edit', methods: ['GET', 'POST'])]
public function addOrEdit(Company $company = null, Request $request, EntityManagerInterface $em): Response
{
$user = $this->getUser();
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$message = 'Société modifié avec succès';
$messageType = 'info';
if (is_null($company)) {
$company = new Company();
$message = 'Société ajouté avec succès';
$messageType = 'success';
}
$form = $this->createForm(CompanyType::class, $company);
$form->handleRequest($request);
// dd($form);
if ($form->isSubmitted() && $form->isValid()) {
// dd($form);
// Ajout de Contacts
foreach ($company->getCustomers() as $customer) {
$customer->setIsActive(true);
}
$company->setIsActive(true);
// dd($company);
$em->persist($company->setUser($user));
$em->flush();
$this->addFlash($messageType, $message);
return $this->redirectToRoute('company');
}
return $this->renderForm('company/add_or_edit.html.twig', [
'company' => $company,
'form' => $form,
]);
}
#[Route('/company/{slug}', name: 'company_show', methods: ['GET'])]
public function show(Company $company): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$company) {
throw $this->createNotFoundException('La société est introuvable');
}
// dd($company);
return $this->render('company/show.html.twig', [
'company' => $company,
]);
}
#[Route('/company/status/{slug}', name: 'company_status', methods: ['GET'])]
public function status(Company $company, ManagerRegistry $doctrine): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$company) {
throw $this->createNotFoundException('La société est introuvable');
}
$oldStatus = $company->getIsActive();
if ($oldStatus === true) {
$newStatus = false;
} else {
$newStatus = true;
}
$company->setIsActive($newStatus);
$em = $doctrine->getManager();
$em->flush();
return $this->redirectToRoute('company');
}
}
`
Thank's indeed
I try to get a add or edit Company but I can't get and save processes

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 %}

LOGIN form and REGISTRATION form in the same TEMPLATE

I'm trying to make a page including the login form and the registration form.
After doing a
make:user,
make:form (RegisterType),
make:auth (SecurityController).
The connection form is executed perfectly but the registration form does not send to the database and refreshes the page.
If anyone has a solution?
Thank you !
RegisterType :
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
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;
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstname', TextType::class, [
'label' => false,
'constraints' => new Length([
'min' => 2,
'max' => 33
]),
'attr' => [
'class' => 'form__input',
'placeholder' => 'Votre prénom'
]
])
->add('lastname', TextType::class, [
'label' => false,
'constraints' => new Length([
'min' => 2,
'max' => 33
]),
'attr' => [
'class' => 'form__input',
'placeholder' => 'Votre nom'
]
])
->add('email', RepeatedType::class, [
'type' => EmailType::class,
'invalid_message' => "L'adresse email et la confirmation doivent être identique.",
'required' => true,
'first_options' => [
'label' => false,
'attr' => [
'class' => 'form__input',
'placeholder' => 'votre#email.com'
]
],
'second_options' => [
'label' => false,
'attr' => [
'class' => 'form__input',
'placeholder' => 'Confirmer votre adresse email'
]
]
])
->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'Le mot de passe et la confirmation doivent être identique.',
'required' => true,
'first_options' => [
'label' => false,
'attr' => [
'class' => 'form__input',
'placeholder' => 'Votre mot de passe'
]
],
'second_options' => [
'label' => false,
'attr' => [
'class' => 'form__input',
'placeholder' => 'Confirmer votre mot de passe'
]
]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
SecurityController :
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\LoginType;
use App\Form\RegisterType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
private EntityManagerInterface $entityManager;
/**
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #Route("/connexion", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils, Request $request, UserPasswordHasherInterface $hasher): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
$user = new User();
$form = $this->createForm(RegisterType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
$password = $hasher->hashPassword($user, $user->getPassword());
$user->setPassword($password);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
'form' => $form->createView()
]);
}
/**
* #Route("/deconnexion", name="app_logout")
*/
public function logout(): void
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
Template (login.html.twig):
{% extends 'base.html.twig' %}
{% block title %}Connexion{% endblock %}
{% block content %}
<div class="connexion__container">
<div class="connexion__forms">
<div class="connexion__forms-form">
<form name="login" method="post" class="connexion__form-connexion">
<h2 class="connexion__form-title">Connectez vous</h2>
<div class="connexion__form-input">
<i class="uil uil-user"></i>
<input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" autocomplete="email" placeholder="votre#email.com" required autofocus>
</div>
<div class="connexion__form-input">
<i class="uil uil-padlock"></i>
<input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" placeholder="Votre mot de passe" required>
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button class="connexion__form-button" type="submit">Connexion</button>
</form>
{{ form_start(form, {'attr' : {'class' : 'connexion__form-register'} }) }}
<h2 class="connexion__form-title">Inscrivez vous</h2>
<div class="connexion__form-input">
<i class="uil uil-user"></i>
{{ form_widget(form.firstname) }}
</div>
<div class="connexion__form-input">
<i class="uil uil-user"></i>
{{ form_widget(form.lastname) }}
</div>
<div class="connexion__form-input">
<i class="uil uil-envelope"></i>
{{ form_widget(form.email.first) }}
</div>
<div class="connexion__form-input">
<i class="uil uil-envelope"></i>
{{ form_widget(form.email.second) }}
</div>
<div class="connexion__form-input">
<i class="uil uil-padlock"></i>
{{ form_widget(form.password.first) }}
</div>
<div class="connexion__form-input">
<i class="uil uil-padlock"></i>
{{ form_widget(form.password.second) }}
</div>
{{ form_rest(form) }}
<button class="connexion__form-button" type="submit">S'inscrire</button>
{{ form_end(form) }}
</div>
</div>
<div class="connexion__options-container">
<div class="connexion__option connexion__option-left">
<div class="content">
<h3>Première visite ?</h3>
<p>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Debitis,
ex ratione. Aliquid!
</p>
<button class="connexion__form-button connexion__form-buttonAlt" id="connexion__button-register">
Inscrivez vous
</button>
</div>
<img src="{{ asset('assets/img/register.svg') }}" class="image" alt="" />
</div>
<div class="connexion__option connexion__option-right">
<div class="content">
<h3>Vous êtes déjà membre ?</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum
laboriosam ad deleniti.
</p>
<button class="connexion__form-button connexion__form-buttonAlt" id="connexion__button-connexion">
Connectez vous
</button>
</div>
<img src="{{ asset('assets/img/log.svg') }}" class="image" alt="" />
</div>
</div>
</div>
{% endblock %}
Thank you !

Symfony Render a Collection Form Type Prototype

I try to render the collection type prototype I create with an embed collection form.
I read the documentation on Symonfy, Github, ... but don't succeed to understand the way to use the block to render the form.
Here I have a form (RegistrationFormType) based on the User entity which has an embed collection form of Adress (AdressFormType).
I succeed to add the button and generate new adress on the form but I would like to have my prototype with the same layout as the one of my first adress (retrieved from DB).
Could you help me to understand what I do wrong please?
Here my RegistrationFormType
<?php
namespace App\Form;
use App\Entity\User;
//use Doctrine\DBAL\Types\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\CallbackTransformer;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles',ChoiceType::class,[
'choices'=>[
'Specialist'=>'Role_Specialist',
'Parent'=>'Role_Parent',
'Center'=>'Role_Center',],
'label'=>"Je m'inscris en tant que"])
->add('name')
->add('firstname',TextType::class, [
'label' => 'Firstname',
'row_attr' => [
'id' => 'firstname'
],])
->add('email')
->add('phone',TelType::class)
->add('NISS',TextType::class, [
'label' => 'NISS',
'row_attr' => [
'id' => 'NISS'
]
,
'required'=>'false',
])
->add('BCE',TextType::class, [
'label' => 'BCE',
'row_attr' => [
'id' => 'BCE'
],
'required'=>'false'])
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'You should agree to our terms.',
]),
],
])
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
//imbrication de adress dans le formulaire user afin de retrouver toutes les adresses qui lui sont référées
->add('adress',CollectionType::class,[
'entry_type' => AdressType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'block_name' => 'adress'
])
;
// Data transformer
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
AdressType
<?php
namespace App\Form;
use App\Entity\Adress;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type',ChoiceType::class,[
'choices' =>[
'Adresse Principale' => 'Principale',
'Adresse Secondaire' => 'Secondaire'
]
])
->add('housenumber',TextType::class,[
'label'=>'N°'
])
->add('additional_info',TextType::class,[
'label'=>'Apt/Etage/...',
'required'=>false
])
->add('street', TextType::class, [
'label' => 'Rue',
'row_attr' => [
'id' => 'Adress2'
],])
->add('postalcode')
->add('city')
->add('region')
->add('country')
->add('latitude', HiddenType::class)
->add('longitude', HiddenType::class)
// ->add('users')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Adress::class,
]);
}
}
The twig template
{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
{% for flashError in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="container">
{{ form_start(registrationForm) }}
<div class="form-group">{{ form_row(registrationForm.roles) }}</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.name) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.firstname) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.email) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-4">{{ form_row(registrationForm.phone) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.NISS, {label: 'NISS'}) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.BCE, {label: 'BCE'}) }}</div>
</div>
{# #TODO formatter pour qu'on differencie bien les adresses entre elles par block. Cf block en dessous#}
<h3>Adresses</h3>
{% block _registration_form_adress_entry_widget %}
<div class="adress" data-prototype="{{ form_widget(registrationForm.adress.vars.prototype)|e('html_attr') }}">
{% for adress in registrationForm.adress %}
<div class="form-group col-md-4">{{ form_row(adress.type) }}</div>
<div class="form-row">
<div class="form-group col-md-4">{{ form_row(adress.street) }}</div>
<div class="form-group col-md-4">{{ form_row(adress.housenumber) }}</div>
<div class="form-group col-md-4">{{ form_row(adress.additional_info) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-3">{{ form_row(adress.postalcode) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.city) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.region) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.country) }}</div>
</div>
{% endfor %}
</div>
<button type="button" class="add_item_link" data-collection-holder-class="adress">Add an adress</button>
{{ form_end(registrationForm) }}
{% endblock %}
</div>
<script>
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('li');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
document
.querySelectorAll('.add_item_link')
.forEach(btn => btn.addEventListener("click", addFormToCollection));
</script>
{% endblock %}
{#{% block registration_form_adress_entry_row %}#}
{# <div class="form-row">#}
{# <div class="form-group col-md-4">{{ form_row(adress.street) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(adress.housenumber) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(adress.additional_info) }}</div>#}
{# </div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-3">{{ form_row(adress.postalcode) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.city) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.region) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.country) }}</div>#}
{# </div>#}
{#{% endblock %}#}
The code rendering
Thank you :)
You can define a macro that will render both the existing subfields of the form and the prototype string.
{% import _self as formMacros %}
{% macro address(item) %}
... your code formatting the address subform ...
{% endmacro %}
So this line in twig:
<div class="adress" data-prototype="{{ form_widget(registrationForm.adress.vars.prototype)|e('html_attr') }}">
would be
<div class="adress" data-prototype="{{ formMacros.address(registrationForm.adress.vars.prototype)|e('html_attr') }}">
and same thing for the existing fields.

Multiple forms in Symfony. Display works but multiple persist

I have a page with forms in hiddens modals.
Forms come from the same entity with different ids.
Displaying the modal works. For each modal I want to display, datas are ok inside inputs.
The issue is : when I submit the form, every forms (hidden included) are persisted.
//EditServiceType.php
namespace Guillaume\PartnerManagerBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class EditServiceType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label_attr' => array('class' => 'col-form-label'),
'attr' => array('class' => 'form-control')
))
->add('type')
->add('version')
->add('connection_type', ChoiceType::class, array(
'choices' => array(
'Connected' => 2,
'Local' => 1,
)));
}/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Guillaume\PartnerManagerBundle\Entity\Service'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'edit_guillaume_partnermanagerbundle_service';
}
}
Part of my Controller
$formEdits = [];
$forms = [];
$serviceFormEdits = [];
foreach($services as $service) {
$serviceFormEdits[$service->getId()] = $em->getRepository('GuillaumePartnerManagerBundle:Service')->find($service);
$forms[$service->getId()] = $this->createForm('Guillaume\PartnerManagerBundle\Form\EditServiceType', $serviceFormEdits[$service->getId()]);
$formEdits[$service->getId()] = $forms[$service->getId()]->createView();
$forms[$service->getId()]->handleRequest($request);
if ($forms[$service->getId()]->isSubmitted() && $forms[$service->getId()]->isValid()) {
$em->persist($serviceFormEdits[$service->getId()]);
$em->flush();
$this->addFlash('notice', 'Service has been edited');
}
}
}
//(...)
return $this->render('GuillaumePartnerManagerBundle:customers:customer.html.twig', [
'formEdits' => $formEdits,
]);
Part of my twig (the modal)
{% for result in results %}
{% form_theme formEdits[result.service.id] 'bootstrap_4_horizontal_layout.html.twig' %}
<div class="modal fade" id="editEnvironment-{{ result.service.id }}" tabindex="-1" role="dialog"
aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="exampleModalLongTitle">{{ 'Edit Environment'|trans }}</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
{{ form_start(formEdits[result.service.id]) }}
<div class="form-group row">
{{ form_widget(formEdits[result.service.id]) }}
</div>
<div class="modal-footer row">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" value="cancel" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" value="save" class="btn btn-primary">{{ 'Validate'|trans }}</button>
</div>
</div>
{{ form_end(formEdits[result.service.id]) }}
</div>
</div>
</div>
</div>
{% endfor %}
Finally I figured it out.
adding 'id' in options resolver
$forms[$service->getId()] = $this->createForm('Guillaume\PartnerManagerBundle\Form\EditServiceType', $serviceFormEdits[$service->getId()], ['id' => $service->getId()]);
adding input hidden in formType
->add('id', HiddenType::class, array(
'data' => $options['id'],
'mapped' => false,
))
and I use this id when submit is valid
$postData = $request->request->get('edit_guillaume_partnermanagerbundle_service');
$postId = $postData['id'];
$forms[$postId]->handleRequest($request);
if ($forms[$postId]->isSubmitted() && $forms[$postId]->isValid()) {
$em->persist($serviceFormEdits[$postId]);
$em->flush($serviceFormEdits[$postId]);
//(...)

Symfony2 collection always empty

I have an issue that I haven't been able to solve. I have 2 entities:
<?php
namespace ...\Entity;
// ...
/**
* Pregunta
*
* #ORM\Table(name="pregunta", indexes={#ORM\Index(name="fk_respuesta_tipo_respuesta1_idx", columns={"tipo_respuesta_id"}))})
* #ORM\Entity
*/
class Pregunta {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="...\Entity\Respuesta", mappedBy="pregunta")
*/
private $respuesta;
public function __construct() {
$this->tipoPrueba = new \Doctrine\Common\Collections\ArrayCollection();
$this->respuesta = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add respuesta
*
* #param ...\Entity\Respuesta $respuesta
* #return Pregunta
*/
public function addRespuesta(...\Entity\Respuesta $respuesta) {
$this->respuesta[] = $respuesta;
return $this;
}
/**
* Remove respuesta
*
* #param ...\Entity\Respuesta $respuesta
*/
public function removeRespuesta(...\Entity\Respuesta $respuesta) {
$this->respuesta->removeElement($respuesta);
}
/**
* Get respuesta
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getRespuesta() {
return $this->respuesta;
}
function setRespuesta(\Doctrine\Common\Collections\Collection $respuesta) {
$this->respuesta = $respuesta;
}
}
Then I have the Respuesta entity:
<?php
class Respuesta {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="texto_respuesta", type="text", nullable=false)
*/
private $textoRespuesta;
// ...
I have a PreguntaType which has its fields and a collection of RespuestaType:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('titulo', 'textarea', array("label" => "Enunciado: ", "required" => true, "attr" => array('class' => 'form-control')))
->add('numeroPagina', 'integer', array("label" => "Página: ", "required" => true, "attr" => array('class' => 'form-control')))
->add('areaConocimiento', 'entity', array('class' => 'UciBaseDatosBundle:AreaConocimiento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('trianguloTalento', 'entity', array('class' => 'UciBaseDatosBundle:TrianguloTalento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('capitulo', 'entity', array('class' => 'UciBaseDatosBundle:Capitulo', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('grupoProcesos', 'entity', array('class' => 'UciBaseDatosBundle:GrupoProcesos', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('tipoPrueba', 'entity', array('class' => 'UciBaseDatosBundle:TipoPrueba', 'expanded' => true, 'multiple' => true, 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('libro', 'entity', array('class' => 'UciBaseDatosBundle:Libro', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('respuesta', 'collection', array(
'type' => new RespuestaType(),
'prototype' => true,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'label' => ' '
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Uci\Bundle\BaseDatosBundle\Entity\Pregunta'
));
}
However, when I debug my form submission, if i set my collection to 'required' => true, it throws this error An invalid form control with name='...[respuesta][Respuesta0][RespuestaField]' is not focusable. On the other hand, If I set it to 'required' => false, my collections' fields are always empty.
This is my TWIG file:
<form action="{{ path('uci_administrador_registrarPregunta', { 'idTipoRespuesta': tipoRespuesta.id }) }}" name="formulario" method="POST" enctype="multipart/form-data">
<h3 class="thin text-center">Registrar una nueva pregunta {{ tipoRespuesta.nombre }}</h3>
<p class="text-center text-muted">{{ tipoRespuesta.explicacion }}</p>
<hr>
{% if error %}
<div style="color:red">{{ error }}</div>
{% endif %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.titulo) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.numeroPagina) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.areaConocimiento) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.capitulo) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.grupoProcesos) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.trianguloTalento) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.tipoPrueba) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.libro) }}
</div>
</div>
<br>
<hr>
<h3>Respuestas</h3><br>
<div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for respuesta in form.respuesta %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(respuesta.correcta) }}
</div>
</div>
{% endfor %}
</div>
<br><br>
<div class="row">
<div class="col-lg-8">
</div>
<div class="col-lg-4 text-right">
<button class="btn btn-action" type="submit">Registrar</button>
</div>
</div>
{{ form_rest(form) }}
</form>
I use some javascript to add my collection forms:
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Añadir respuesta');
var $newLinkLi = $('<div></div>').append($addTagLink);
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, 'Respuesta' + index);
// increase the index with one for the next item
$collectionHolder.data('index', $collectionHolder.find(':input').length);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<div style="background-color:#F6F6F6; border-radius:10px;padding: 25px;border: 5px solid #003c70;margin: 5px;"></div><br>').append(newForm);
$newLinkLi.before($newFormLi);
}
document.ready = function () {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.respuestas');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addTagLink.on('click', function (e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
// ...
}
I would really appreciate any help.
Thanks.
My problem was in my javascript. I don´t know why my previous code didn't work but I changed and it worked. My javascript is the following:
var collectionHolder = $('#respuestas');
var prototype = collectionHolder.attr('data-prototype');
var form = prototype.replace(/__name__/g, collectionHolder.children().length); //importante
var removeFormA = $('Borrar');
var newLi = $('<li></li>');
newLi.append(form);
newLi.append(removeFormA);
collectionHolder.append(newLi);
And this is part of my TWIG file:
<ul id="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{% for respuesta in form.respuesta %}
<li> {{ form_row(respuesta) }}</li>
{% endfor %}
</ul>
I hope it helps someone else.
Regards.
Are you trying to hide some fields in your form ? It looks like some fields are required but not actually displayed in the page, which prevents the browser from validating the form. Refer to that answer : https://stackoverflow.com/a/28340579/4114297
I wonder why you're only rendering respuesta.correcta in the loop. You might want to render the while respuesta item :
<div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for respuesta in form.respuesta %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(respuesta) }} {# <- Here #}
</div>
</div>
{% endfor %}
</div>
If you need some fields to be hidden and/or pre-filled, you could do that RespuestaType